Merge in latest from sanic-routing branch
This commit is contained in:
commit
973c315790
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '25 16 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'python' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
7
Makefile
7
Makefile
|
@ -63,17 +63,14 @@ ifdef include_tests
|
||||||
isort -rc sanic tests
|
isort -rc sanic tests
|
||||||
else
|
else
|
||||||
$(info Sorting Imports)
|
$(info Sorting Imports)
|
||||||
isort -rc sanic tests
|
isort -rc sanic tests --profile=black
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
black:
|
black:
|
||||||
black --config ./.black.toml sanic tests
|
black --config ./.black.toml sanic tests
|
||||||
|
|
||||||
isort:
|
pretty: beautify
|
||||||
isort sanic tests
|
|
||||||
|
|
||||||
pretty: black isort
|
|
||||||
|
|
||||||
docs-clean:
|
docs-clean:
|
||||||
cd docs && make clean
|
cd docs && make clean
|
||||||
|
|
|
@ -6,14 +6,15 @@ Sanic releases long term support release once a year in December. LTS releases r
|
||||||
|
|
||||||
| Version | LTS | Supported |
|
| Version | LTS | Supported |
|
||||||
| ------- | ------------- | ------------------ |
|
| ------- | ------------- | ------------------ |
|
||||||
| 20.9 | | :heavy_check_mark: |
|
| 20.12 | until 2022-12 | :heavy_check_mark: |
|
||||||
|
| 20.9 | | :x: |
|
||||||
| 20.6 | | :x: |
|
| 20.6 | | :x: |
|
||||||
| 20.3 | | :x: |
|
| 20.3 | | :x: |
|
||||||
| 19.12 | until 2021-12 | :white_check_mark: |
|
| 19.12 | until 2021-12 | :white_check_mark: |
|
||||||
| 19.9 | | :x: |
|
| 19.9 | | :x: |
|
||||||
| 19.6 | | :x: |
|
| 19.6 | | :x: |
|
||||||
| 19.3 | | :x: |
|
| 19.3 | | :x: |
|
||||||
| 18.12 | until 2020-12 | :white_check_mark: |
|
| 18.12 | | :x: |
|
||||||
| 0.8.3 | | :x: |
|
| 0.8.3 | | :x: |
|
||||||
| 0.7.0 | | :x: |
|
| 0.7.0 | | :x: |
|
||||||
| 0.6.0 | | :x: |
|
| 0.6.0 | | :x: |
|
||||||
|
|
339
sanic/app.py
339
sanic/app.py
|
@ -25,27 +25,24 @@ from typing import (
|
||||||
)
|
)
|
||||||
from urllib.parse import urlencode, urlunparse
|
from urllib.parse import urlencode, urlunparse
|
||||||
|
|
||||||
from sanic_routing.route import Route
|
from sanic_routing.exceptions import FinalizationError # type: ignore
|
||||||
|
from sanic_routing.route import Route # type: ignore
|
||||||
|
|
||||||
from sanic import reloader_helpers
|
from sanic import reloader_helpers
|
||||||
from sanic.asgi import ASGIApp
|
from sanic.asgi import ASGIApp
|
||||||
|
from sanic.base import BaseSanic
|
||||||
from sanic.blueprint_group import BlueprintGroup
|
from sanic.blueprint_group import BlueprintGroup
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
from sanic.config import BASE_LOGO, Config
|
from sanic.config import BASE_LOGO, Config
|
||||||
from sanic.exceptions import (
|
from sanic.exceptions import (
|
||||||
InvalidUsage,
|
InvalidUsage,
|
||||||
NotFound,
|
|
||||||
SanicException,
|
SanicException,
|
||||||
ServerError,
|
ServerError,
|
||||||
URLBuildError,
|
URLBuildError,
|
||||||
)
|
)
|
||||||
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.base import BaseMixin
|
from sanic.mixins.listeners import ListenerEvent
|
||||||
from sanic.mixins.exceptions import ExceptionMixin
|
|
||||||
from sanic.mixins.listeners import ListenerEvent, ListenerMixin
|
|
||||||
from sanic.mixins.middleware import MiddlewareMixin
|
|
||||||
from sanic.mixins.routes import RouteMixin
|
|
||||||
from sanic.models.futures import (
|
from sanic.models.futures import (
|
||||||
FutureException,
|
FutureException,
|
||||||
FutureListener,
|
FutureListener,
|
||||||
|
@ -68,9 +65,7 @@ from sanic.static import register as static_register
|
||||||
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
||||||
|
|
||||||
|
|
||||||
class Sanic(
|
class Sanic(BaseSanic):
|
||||||
BaseMixin, RouteMixin, MiddlewareMixin, ListenerMixin, ExceptionMixin
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
The main application instance
|
The main application instance
|
||||||
"""
|
"""
|
||||||
|
@ -103,9 +98,7 @@ class Sanic(
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.asgi = False
|
self.asgi = False
|
||||||
self.router = router or Router(
|
self.router = router or Router()
|
||||||
exception=NotFound, method_handler_exception=NotFound
|
|
||||||
)
|
|
||||||
self.request_class = request_class
|
self.request_class = request_class
|
||||||
self.error_handler = error_handler or ErrorHandler()
|
self.error_handler = error_handler or ErrorHandler()
|
||||||
self.config = Config(load_env=load_env)
|
self.config = Config(load_env=load_env)
|
||||||
|
@ -124,6 +117,9 @@ class Sanic(
|
||||||
self.websocket_tasks: Set[Future] = set()
|
self.websocket_tasks: Set[Future] = set()
|
||||||
self.named_request_middleware: Dict[str, Deque[MiddlewareType]] = {}
|
self.named_request_middleware: Dict[str, Deque[MiddlewareType]] = {}
|
||||||
self.named_response_middleware: Dict[str, Deque[MiddlewareType]] = {}
|
self.named_response_middleware: Dict[str, Deque[MiddlewareType]] = {}
|
||||||
|
# self.named_request_middleware: Dict[str, MiddlewareType] = {}
|
||||||
|
# self.named_response_middleware: Dict[str, MiddlewareType] = {}
|
||||||
|
self._test_manager = None
|
||||||
self._test_client = None
|
self._test_client = None
|
||||||
self._asgi_client = None
|
self._asgi_client = None
|
||||||
# Register alternative method names
|
# Register alternative method names
|
||||||
|
@ -135,6 +131,8 @@ class Sanic(
|
||||||
if self.config.REGISTER:
|
if self.config.REGISTER:
|
||||||
self.__class__.register_app(self)
|
self.__class__.register_app(self)
|
||||||
|
|
||||||
|
self.router.ctx.app = self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loop(self):
|
def loop(self):
|
||||||
"""
|
"""
|
||||||
|
@ -175,10 +173,6 @@ class Sanic(
|
||||||
partial(self._loop_add_task, task)
|
partial(self._loop_add_task, task)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Decorator
|
|
||||||
def _apply_listener(self, listener: FutureListener):
|
|
||||||
return self.register_listener(listener.listener, listener.event)
|
|
||||||
|
|
||||||
def register_listener(self, listener: Callable, event: str) -> Any:
|
def register_listener(self, listener: Callable, event: str) -> Any:
|
||||||
"""
|
"""
|
||||||
Register the listener for a given event.
|
Register the listener for a given event.
|
||||||
|
@ -197,42 +191,6 @@ class Sanic(
|
||||||
self.listeners[_event].append(listener)
|
self.listeners[_event].append(listener)
|
||||||
return listener
|
return listener
|
||||||
|
|
||||||
def _apply_route(self, route: FutureRoute) -> Route:
|
|
||||||
return self.router.add(**route._asdict())
|
|
||||||
|
|
||||||
def _apply_static(self, static: FutureStatic) -> Route:
|
|
||||||
return static_register(self, static)
|
|
||||||
|
|
||||||
def enable_websocket(self, enable: bool = True):
|
|
||||||
"""
|
|
||||||
Enable or disable the support for websocket.
|
|
||||||
|
|
||||||
Websocket is enabled automatically if websocket routes are
|
|
||||||
added to the application.
|
|
||||||
"""
|
|
||||||
if not self.websocket_enabled:
|
|
||||||
# if the server is stopped, we want to cancel any ongoing
|
|
||||||
# websocket tasks, to allow the server to exit promptly
|
|
||||||
self.listener("before_server_stop")(self._cancel_websocket_tasks)
|
|
||||||
|
|
||||||
self.websocket_enabled = enable
|
|
||||||
|
|
||||||
# Decorator
|
|
||||||
def _apply_exception_handler(self, handler: FutureException):
|
|
||||||
"""Decorate a function to be registered as a handler for exceptions
|
|
||||||
|
|
||||||
:param exceptions: exceptions
|
|
||||||
:return: decorated function
|
|
||||||
"""
|
|
||||||
|
|
||||||
for exception in handler.exceptions:
|
|
||||||
if isinstance(exception, (tuple, list)):
|
|
||||||
for e in exception:
|
|
||||||
self.error_handler.add(e, handler.handler)
|
|
||||||
else:
|
|
||||||
self.error_handler.add(exception, handler.handler)
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def register_middleware(self, middleware, attach_to: str = "request"):
|
def register_middleware(self, middleware, attach_to: str = "request"):
|
||||||
"""
|
"""
|
||||||
Register an application level middleware that will be attached
|
Register an application level middleware that will be attached
|
||||||
|
@ -288,13 +246,49 @@ class Sanic(
|
||||||
if middleware not in self.named_response_middleware[_rn]:
|
if middleware not in self.named_response_middleware[_rn]:
|
||||||
self.named_response_middleware[_rn].appendleft(middleware)
|
self.named_response_middleware[_rn].appendleft(middleware)
|
||||||
|
|
||||||
# Decorator
|
def _apply_exception_handler(self, handler: FutureException):
|
||||||
|
"""Decorate a function to be registered as a handler for exceptions
|
||||||
|
|
||||||
|
:param exceptions: exceptions
|
||||||
|
:return: decorated function
|
||||||
|
"""
|
||||||
|
|
||||||
|
for exception in handler.exceptions:
|
||||||
|
if isinstance(exception, (tuple, list)):
|
||||||
|
for e in exception:
|
||||||
|
self.error_handler.add(e, handler.handler)
|
||||||
|
else:
|
||||||
|
self.error_handler.add(exception, handler.handler)
|
||||||
|
return handler
|
||||||
|
|
||||||
|
def _apply_listener(self, listener: FutureListener):
|
||||||
|
return self.register_listener(listener.listener, listener.event)
|
||||||
|
|
||||||
|
def _apply_route(self, route: FutureRoute) -> Route:
|
||||||
|
params = route._asdict()
|
||||||
|
websocket = params.pop("websocket", False)
|
||||||
|
subprotocols = params.pop("subprotocols", None)
|
||||||
|
|
||||||
|
if websocket:
|
||||||
|
self.enable_websocket()
|
||||||
|
websocket_handler = partial(
|
||||||
|
self._websocket_handler,
|
||||||
|
route.handler,
|
||||||
|
subprotocols=subprotocols,
|
||||||
|
)
|
||||||
|
websocket_handler.__name__ = route.handler.__name__ # type: ignore
|
||||||
|
websocket_handler.is_websocket = True # type: ignore
|
||||||
|
params["handler"] = websocket_handler
|
||||||
|
return self.router.add(**params)
|
||||||
|
|
||||||
|
def _apply_static(self, static: FutureStatic) -> Route:
|
||||||
|
return static_register(self, static)
|
||||||
|
|
||||||
def _apply_middleware(
|
def _apply_middleware(
|
||||||
self,
|
self,
|
||||||
middleware: FutureMiddleware,
|
middleware: FutureMiddleware,
|
||||||
route_names: Optional[List[str]] = None,
|
route_names: Optional[List[str]] = None,
|
||||||
):
|
):
|
||||||
print(f"{middleware=}")
|
|
||||||
if route_names:
|
if route_names:
|
||||||
return self.register_named_middleware(
|
return self.register_named_middleware(
|
||||||
middleware.middleware, route_names, middleware.attach_to
|
middleware.middleware, route_names, middleware.attach_to
|
||||||
|
@ -304,6 +298,19 @@ class Sanic(
|
||||||
middleware.middleware, middleware.attach_to
|
middleware.middleware, middleware.attach_to
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def enable_websocket(self, enable=True):
|
||||||
|
"""Enable or disable the support for websocket.
|
||||||
|
|
||||||
|
Websocket is enabled automatically if websocket routes are
|
||||||
|
added to the application.
|
||||||
|
"""
|
||||||
|
if not self.websocket_enabled:
|
||||||
|
# if the server is stopped, we want to cancel any ongoing
|
||||||
|
# websocket tasks, to allow the server to exit promptly
|
||||||
|
self.listener("before_server_stop")(self._cancel_websocket_tasks)
|
||||||
|
|
||||||
|
self.websocket_enabled = enable
|
||||||
|
|
||||||
def blueprint(self, blueprint, **options):
|
def blueprint(self, blueprint, **options):
|
||||||
"""Register a blueprint on the application.
|
"""Register a blueprint on the application.
|
||||||
|
|
||||||
|
@ -323,6 +330,12 @@ class Sanic(
|
||||||
else:
|
else:
|
||||||
self.blueprints[blueprint.name] = blueprint
|
self.blueprints[blueprint.name] = blueprint
|
||||||
self._blueprint_order.append(blueprint)
|
self._blueprint_order.append(blueprint)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.strict_slashes is not None
|
||||||
|
and blueprint.strict_slashes is None
|
||||||
|
):
|
||||||
|
blueprint.strict_slashes = self.strict_slashes
|
||||||
blueprint.register(self, options)
|
blueprint.register(self, options)
|
||||||
|
|
||||||
def url_for(self, view_name: str, **kwargs):
|
def url_for(self, view_name: str, **kwargs):
|
||||||
|
@ -351,30 +364,28 @@ class Sanic(
|
||||||
# find the route by the supplied view name
|
# find the route by the supplied view name
|
||||||
kw: Dict[str, str] = {}
|
kw: Dict[str, str] = {}
|
||||||
# special static files url_for
|
# special static files url_for
|
||||||
if view_name == "static":
|
|
||||||
kw.update(name=kwargs.pop("name", "static"))
|
if "." not in view_name:
|
||||||
elif view_name.endswith(".static"): # blueprint.static
|
view_name = f"{self.name}.{view_name}"
|
||||||
kwargs.pop("name", None)
|
|
||||||
|
if view_name.endswith(".static"):
|
||||||
|
name = kwargs.pop("name", None)
|
||||||
|
if name:
|
||||||
|
view_name = view_name.replace("static", name)
|
||||||
kw.update(name=view_name)
|
kw.update(name=view_name)
|
||||||
|
|
||||||
uri, route = self.router.find_route_by_view_name(view_name, **kw)
|
route = self.router.find_route_by_view_name(view_name, **kw)
|
||||||
if not (uri and route):
|
if not route:
|
||||||
raise URLBuildError(
|
raise URLBuildError(
|
||||||
f"Endpoint with name `{view_name}` was not found"
|
f"Endpoint with name `{view_name}` was not found"
|
||||||
)
|
)
|
||||||
|
|
||||||
# If the route has host defined, split that off
|
uri = route.path
|
||||||
# TODO: Retain netloc and path separately in Route objects
|
|
||||||
host = uri.find("/")
|
|
||||||
if host > 0:
|
|
||||||
host, uri = uri[:host], uri[host:]
|
|
||||||
else:
|
|
||||||
host = None
|
|
||||||
|
|
||||||
if view_name == "static" or view_name.endswith(".static"):
|
if getattr(route.ctx, "static", None):
|
||||||
filename = kwargs.pop("filename", None)
|
filename = kwargs.pop("filename", "")
|
||||||
# it's static folder
|
# it's static folder
|
||||||
if "<file_uri:" in uri:
|
if "file_uri" in uri:
|
||||||
folder_ = uri.split("<file_uri:", 1)[0]
|
folder_ = uri.split("<file_uri:", 1)[0]
|
||||||
if folder_.endswith("/"):
|
if folder_.endswith("/"):
|
||||||
folder_ = folder_[:-1]
|
folder_ = folder_[:-1]
|
||||||
|
@ -382,22 +393,36 @@ class Sanic(
|
||||||
if filename.startswith("/"):
|
if filename.startswith("/"):
|
||||||
filename = filename[1:]
|
filename = filename[1:]
|
||||||
|
|
||||||
uri = f"{folder_}/{filename}"
|
kwargs["file_uri"] = filename
|
||||||
|
|
||||||
if uri != "/" and uri.endswith("/"):
|
if uri != "/" and uri.endswith("/"):
|
||||||
uri = uri[:-1]
|
uri = uri[:-1]
|
||||||
|
|
||||||
out = uri
|
if not uri.startswith("/"):
|
||||||
|
uri = f"/{uri}"
|
||||||
|
|
||||||
# find all the parameters we will need to build in the URL
|
out = uri
|
||||||
matched_params = re.findall(self.router.parameter_pattern, uri)
|
|
||||||
|
|
||||||
# _method is only a placeholder now, don't know how to support it
|
# _method is only a placeholder now, don't know how to support it
|
||||||
kwargs.pop("_method", None)
|
kwargs.pop("_method", None)
|
||||||
anchor = kwargs.pop("_anchor", "")
|
anchor = kwargs.pop("_anchor", "")
|
||||||
# _external need SERVER_NAME in config or pass _server arg
|
# _external need SERVER_NAME in config or pass _server arg
|
||||||
external = kwargs.pop("_external", False)
|
host = kwargs.pop("_host", None)
|
||||||
|
external = kwargs.pop("_external", False) or bool(host)
|
||||||
scheme = kwargs.pop("_scheme", "")
|
scheme = kwargs.pop("_scheme", "")
|
||||||
|
if route.ctx.hosts and external:
|
||||||
|
if not host and len(route.ctx.hosts) > 1:
|
||||||
|
raise ValueError(
|
||||||
|
f"Host is ambiguous: {', '.join(route.ctx.hosts)}"
|
||||||
|
)
|
||||||
|
elif host and host not in route.ctx.hosts:
|
||||||
|
raise ValueError(
|
||||||
|
f"Requested host ({host}) is not available for this "
|
||||||
|
f"route: {route.ctx.hosts}"
|
||||||
|
)
|
||||||
|
elif not host:
|
||||||
|
host = list(route.ctx.hosts)[0]
|
||||||
|
|
||||||
if scheme and not external:
|
if scheme and not external:
|
||||||
raise ValueError("When specifying _scheme, _external must be True")
|
raise ValueError("When specifying _scheme, _external must be True")
|
||||||
|
|
||||||
|
@ -415,44 +440,44 @@ class Sanic(
|
||||||
if "://" in netloc[:8]:
|
if "://" in netloc[:8]:
|
||||||
netloc = netloc.split("://", 1)[-1]
|
netloc = netloc.split("://", 1)[-1]
|
||||||
|
|
||||||
for match in matched_params:
|
# find all the parameters we will need to build in the URL
|
||||||
name, _type, pattern = self.router.parse_parameter_string(match)
|
# matched_params = re.findall(self.router.parameter_pattern, uri)
|
||||||
|
route.finalize()
|
||||||
|
for param_info in route.params.values():
|
||||||
|
# name, _type, pattern = self.router.parse_parameter_string(match)
|
||||||
# we only want to match against each individual parameter
|
# we only want to match against each individual parameter
|
||||||
specific_pattern = f"^{pattern}$"
|
|
||||||
supplied_param = None
|
|
||||||
|
|
||||||
if name in kwargs:
|
try:
|
||||||
supplied_param = kwargs.get(name)
|
supplied_param = str(kwargs.pop(param_info.name))
|
||||||
del kwargs[name]
|
except KeyError:
|
||||||
else:
|
|
||||||
raise URLBuildError(
|
raise URLBuildError(
|
||||||
f"Required parameter `{name}` was not passed to url_for"
|
f"Required parameter `{param_info.name}` was not "
|
||||||
|
"passed to url_for"
|
||||||
)
|
)
|
||||||
|
|
||||||
supplied_param = str(supplied_param)
|
# determine if the parameter supplied by the caller
|
||||||
# determine if the parameter supplied by the caller passes the test
|
# passes the test in the URL
|
||||||
# in the URL
|
if param_info.pattern:
|
||||||
passes_pattern = re.match(specific_pattern, supplied_param)
|
passes_pattern = param_info.pattern.match(supplied_param)
|
||||||
|
if not passes_pattern:
|
||||||
if not passes_pattern:
|
if param_info.cast != str:
|
||||||
if _type != str:
|
msg = (
|
||||||
type_name = _type.__name__
|
f'Value "{supplied_param}" '
|
||||||
|
f"for parameter `{param_info.name}` does "
|
||||||
msg = (
|
"not match pattern for type "
|
||||||
f'Value "{supplied_param}" '
|
f"`{param_info.cast.__name__}`: "
|
||||||
f"for parameter `{name}` does not "
|
f"{param_info.pattern.pattern}"
|
||||||
f"match pattern for type `{type_name}`: {pattern}"
|
)
|
||||||
)
|
else:
|
||||||
else:
|
msg = (
|
||||||
msg = (
|
f'Value "{supplied_param}" for parameter '
|
||||||
f'Value "{supplied_param}" for parameter `{name}` '
|
f"`{param_info.name}` does not satisfy "
|
||||||
f"does not satisfy pattern {pattern}"
|
f"pattern {param_info.pattern.pattern}"
|
||||||
)
|
)
|
||||||
raise URLBuildError(msg)
|
raise URLBuildError(msg)
|
||||||
|
|
||||||
# replace the parameter in the URL with the supplied value
|
# replace the parameter in the URL with the supplied value
|
||||||
replacement_regex = f"(<{name}.*?>)"
|
replacement_regex = f"(<{param_info.name}.*?>)"
|
||||||
|
|
||||||
out = re.sub(replacement_regex, supplied_param, out)
|
out = re.sub(replacement_regex, supplied_param, out)
|
||||||
|
|
||||||
# parse the remainder of the keyword arguments into a querystring
|
# parse the remainder of the keyword arguments into a querystring
|
||||||
|
@ -545,14 +570,13 @@ class Sanic(
|
||||||
# Fetch handler from router
|
# Fetch handler from router
|
||||||
(
|
(
|
||||||
handler,
|
handler,
|
||||||
args,
|
|
||||||
kwargs,
|
kwargs,
|
||||||
uri,
|
uri,
|
||||||
name,
|
name,
|
||||||
endpoint,
|
|
||||||
ignore_body,
|
ignore_body,
|
||||||
) = self.router.get(request)
|
) = self.router.get(request)
|
||||||
request.name = name
|
request.name = name
|
||||||
|
request._match_info = kwargs
|
||||||
|
|
||||||
if (
|
if (
|
||||||
request.stream
|
request.stream
|
||||||
|
@ -578,7 +602,7 @@ class Sanic(
|
||||||
# Execute Handler
|
# Execute Handler
|
||||||
# -------------------------------------------- #
|
# -------------------------------------------- #
|
||||||
|
|
||||||
request.uri_template = uri
|
request.uri_template = f"/{uri}"
|
||||||
if handler is None:
|
if handler is None:
|
||||||
raise ServerError(
|
raise ServerError(
|
||||||
(
|
(
|
||||||
|
@ -587,10 +611,10 @@ class Sanic(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
request.endpoint = endpoint
|
request.endpoint = request.name
|
||||||
|
|
||||||
# Run response handler
|
# Run response handler
|
||||||
response = handler(request, *args, **kwargs)
|
response = handler(request, **kwargs)
|
||||||
if isawaitable(response):
|
if isawaitable(response):
|
||||||
response = await response
|
response = await response
|
||||||
if response:
|
if response:
|
||||||
|
@ -615,26 +639,60 @@ class Sanic(
|
||||||
except CancelledError:
|
except CancelledError:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# -------------------------------------------- #
|
|
||||||
# Response Generation Failed
|
# Response Generation Failed
|
||||||
# -------------------------------------------- #
|
|
||||||
await self.handle_exception(request, e)
|
await self.handle_exception(request, e)
|
||||||
|
|
||||||
|
async def _websocket_handler(
|
||||||
|
self, handler, request, *args, subprotocols=None, **kwargs
|
||||||
|
):
|
||||||
|
request.app = self
|
||||||
|
if not getattr(handler, "__blueprintname__", False):
|
||||||
|
request.endpoint = handler.__name__
|
||||||
|
else:
|
||||||
|
request.endpoint = (
|
||||||
|
getattr(handler, "__blueprintname__", "") + handler.__name__
|
||||||
|
)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
if self.asgi:
|
||||||
|
ws = request.transport.get_websocket_connection()
|
||||||
|
else:
|
||||||
|
protocol = request.transport.get_protocol()
|
||||||
|
protocol.app = self
|
||||||
|
|
||||||
|
ws = await protocol.websocket_handshake(request, subprotocols)
|
||||||
|
|
||||||
|
# schedule the application handler
|
||||||
|
# its future is kept in self.websocket_tasks in case it
|
||||||
|
# needs to be cancelled due to the server being stopped
|
||||||
|
fut = ensure_future(handler(request, ws, *args, **kwargs))
|
||||||
|
self.websocket_tasks.add(fut)
|
||||||
|
try:
|
||||||
|
await fut
|
||||||
|
except (CancelledError, ConnectionClosed):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self.websocket_tasks.remove(fut)
|
||||||
|
await ws.close()
|
||||||
|
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
# Testing
|
# Testing
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def test_client(self):
|
def test_client(self): # noqa
|
||||||
if self._test_client:
|
if self._test_client:
|
||||||
return self._test_client
|
return self._test_client
|
||||||
|
elif self._test_manager:
|
||||||
|
return self._test_manager.test_client
|
||||||
from sanic_testing.testing import SanicTestClient # type: ignore
|
from sanic_testing.testing import SanicTestClient # type: ignore
|
||||||
|
|
||||||
self._test_client = SanicTestClient(self)
|
self._test_client = SanicTestClient(self)
|
||||||
return self._test_client
|
return self._test_client
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def asgi_client(self):
|
def asgi_client(self): # noqa
|
||||||
"""
|
"""
|
||||||
A testing client that uses ASGI to reach into the application to
|
A testing client that uses ASGI to reach into the application to
|
||||||
execute hanlers.
|
execute hanlers.
|
||||||
|
@ -644,6 +702,8 @@ class Sanic(
|
||||||
"""
|
"""
|
||||||
if self._asgi_client:
|
if self._asgi_client:
|
||||||
return self._asgi_client
|
return self._asgi_client
|
||||||
|
elif self._test_manager:
|
||||||
|
return self._test_manager.asgi_client
|
||||||
from sanic_testing.testing import SanicASGITestClient # type: ignore
|
from sanic_testing.testing import SanicASGITestClient # type: ignore
|
||||||
|
|
||||||
self._asgi_client = SanicASGITestClient(self)
|
self._asgi_client = SanicASGITestClient(self)
|
||||||
|
@ -915,7 +975,11 @@ class Sanic(
|
||||||
):
|
):
|
||||||
"""Helper function used by `run` and `create_server`."""
|
"""Helper function used by `run` and `create_server`."""
|
||||||
|
|
||||||
self.router.finalize()
|
try:
|
||||||
|
self.router.finalize()
|
||||||
|
except FinalizationError as e:
|
||||||
|
if not Sanic.test_mode:
|
||||||
|
raise e
|
||||||
|
|
||||||
if isinstance(ssl, dict):
|
if isinstance(ssl, dict):
|
||||||
# try common aliaseses
|
# try common aliaseses
|
||||||
|
@ -950,9 +1014,7 @@ class Sanic(
|
||||||
"backlog": backlog,
|
"backlog": backlog,
|
||||||
}
|
}
|
||||||
|
|
||||||
# -------------------------------------------- #
|
|
||||||
# Register start/stop events
|
# Register start/stop events
|
||||||
# -------------------------------------------- #
|
|
||||||
|
|
||||||
for event_name, settings_name, reverse in (
|
for event_name, settings_name, reverse in (
|
||||||
("before_server_start", "before_start", False),
|
("before_server_start", "before_start", False),
|
||||||
|
@ -1014,40 +1076,6 @@ class Sanic(
|
||||||
for task in app.websocket_tasks:
|
for task in app.websocket_tasks:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
|
|
||||||
async def _websocket_handler(
|
|
||||||
self, handler, request, *args, subprotocols=None, **kwargs
|
|
||||||
):
|
|
||||||
request.app = self
|
|
||||||
if not getattr(handler, "__blueprintname__", False):
|
|
||||||
request.endpoint = handler.__name__
|
|
||||||
else:
|
|
||||||
request.endpoint = (
|
|
||||||
getattr(handler, "__blueprintname__", "") + handler.__name__
|
|
||||||
)
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
if self.asgi:
|
|
||||||
ws = request.transport.get_websocket_connection()
|
|
||||||
else:
|
|
||||||
protocol = request.transport.get_protocol()
|
|
||||||
protocol.app = self
|
|
||||||
|
|
||||||
ws = await protocol.websocket_handshake(request, subprotocols)
|
|
||||||
|
|
||||||
# schedule the application handler
|
|
||||||
# its future is kept in self.websocket_tasks in case it
|
|
||||||
# needs to be cancelled due to the server being stopped
|
|
||||||
fut = ensure_future(handler(request, ws, *args, **kwargs))
|
|
||||||
self.websocket_tasks.add(fut)
|
|
||||||
try:
|
|
||||||
await fut
|
|
||||||
except (CancelledError, ConnectionClosed):
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
self.websocket_tasks.remove(fut)
|
|
||||||
await ws.close()
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
# ASGI
|
# ASGI
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
@ -1057,9 +1085,10 @@ class Sanic(
|
||||||
To be ASGI compliant, our instance must be a callable that accepts
|
To be ASGI compliant, our instance must be a callable that accepts
|
||||||
three arguments: scope, receive, send. See the ASGI reference for more
|
three arguments: scope, receive, send. See the ASGI reference for more
|
||||||
details: https://asgi.readthedocs.io/en/latest
|
details: https://asgi.readthedocs.io/en/latest
|
||||||
/"""
|
"""
|
||||||
self.asgi = True
|
self.asgi = True
|
||||||
asgi_app = await ASGIApp.create(self, scope, receive, send)
|
self._asgi_app = await ASGIApp.create(self, scope, receive, send)
|
||||||
|
asgi_app = self._asgi_app
|
||||||
await asgi_app()
|
await asgi_app()
|
||||||
|
|
||||||
_asgi_single_callable = True # We conform to ASGI 3.0 single-callable
|
_asgi_single_callable = True # We conform to ASGI 3.0 single-callable
|
||||||
|
|
|
@ -131,6 +131,7 @@ class Lifespan:
|
||||||
in sequence since the ASGI lifespan protocol only supports a single
|
in sequence since the ASGI lifespan protocol only supports a single
|
||||||
startup event.
|
startup event.
|
||||||
"""
|
"""
|
||||||
|
self.asgi_app.sanic_app.router.finalize()
|
||||||
listeners = self.asgi_app.sanic_app.listeners.get(
|
listeners = self.asgi_app.sanic_app.listeners.get(
|
||||||
"before_server_start", []
|
"before_server_start", []
|
||||||
) + self.asgi_app.sanic_app.listeners.get("after_server_start", [])
|
) + self.asgi_app.sanic_app.listeners.get("after_server_start", [])
|
||||||
|
|
36
sanic/base.py
Normal file
36
sanic/base.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from sanic.mixins.exceptions import ExceptionMixin
|
||||||
|
from sanic.mixins.listeners import ListenerMixin
|
||||||
|
from sanic.mixins.middleware import MiddlewareMixin
|
||||||
|
from sanic.mixins.routes import RouteMixin
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
metaclass=Base,
|
||||||
|
):
|
||||||
|
...
|
|
@ -1,18 +1,12 @@
|
||||||
from collections import defaultdict, namedtuple
|
from collections import defaultdict
|
||||||
from typing import Iterable, Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from sanic.base import BaseSanic
|
||||||
from sanic.blueprint_group import BlueprintGroup
|
from sanic.blueprint_group import BlueprintGroup
|
||||||
from sanic.mixins.base import BaseMixin
|
|
||||||
from sanic.mixins.exceptions import ExceptionMixin
|
|
||||||
from sanic.mixins.listeners import ListenerMixin
|
|
||||||
from sanic.mixins.middleware import MiddlewareMixin
|
|
||||||
from sanic.mixins.routes import RouteMixin
|
|
||||||
from sanic.models.futures import FutureRoute, FutureStatic
|
from sanic.models.futures import FutureRoute, FutureStatic
|
||||||
|
|
||||||
|
|
||||||
class Blueprint(
|
class Blueprint(BaseSanic):
|
||||||
BaseMixin, RouteMixin, MiddlewareMixin, ListenerMixin, ExceptionMixin
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
In *Sanic* terminology, a **Blueprint** is a logical collection of
|
In *Sanic* terminology, a **Blueprint** is a logical collection of
|
||||||
URLs that perform a specific set of tasks which can be identified by
|
URLs that perform a specific set of tasks which can be identified by
|
||||||
|
@ -122,20 +116,35 @@ class Blueprint(
|
||||||
# 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
|
||||||
|
|
||||||
|
strict_slashes = (
|
||||||
|
self.strict_slashes
|
||||||
|
if future.strict_slashes is None
|
||||||
|
and self.strict_slashes is not None
|
||||||
|
else future.strict_slashes
|
||||||
|
)
|
||||||
|
name = app._generate_name(future.name)
|
||||||
|
|
||||||
apply_route = FutureRoute(
|
apply_route = FutureRoute(
|
||||||
future.handler,
|
future.handler,
|
||||||
uri[1:] if uri.startswith("//") else uri,
|
uri[1:] if uri.startswith("//") else uri,
|
||||||
future.methods,
|
future.methods,
|
||||||
future.host or self.host,
|
future.host or self.host,
|
||||||
future.strict_slashes,
|
strict_slashes,
|
||||||
future.stream,
|
future.stream,
|
||||||
future.version or self.version,
|
future.version or self.version,
|
||||||
future.name,
|
name,
|
||||||
future.ignore_body,
|
future.ignore_body,
|
||||||
|
future.websocket,
|
||||||
|
future.subprotocols,
|
||||||
|
future.unquote,
|
||||||
|
future.static,
|
||||||
)
|
)
|
||||||
|
|
||||||
route = app._apply_route(apply_route)
|
route = app._apply_route(apply_route)
|
||||||
routes.append(route)
|
operation = (
|
||||||
|
routes.extend if isinstance(route, list) else routes.append
|
||||||
|
)
|
||||||
|
operation(route)
|
||||||
|
|
||||||
# Static Files
|
# Static Files
|
||||||
for future in self._future_statics:
|
for future in self._future_statics:
|
||||||
|
@ -148,8 +157,9 @@ class Blueprint(
|
||||||
route_names = [route.name for route in routes if route]
|
route_names = [route.name for route in routes if route]
|
||||||
|
|
||||||
# Middleware
|
# Middleware
|
||||||
for future in self._future_middleware:
|
if route_names:
|
||||||
app._apply_middleware(future, route_names)
|
for future in self._future_middleware:
|
||||||
|
app._apply_middleware(future, route_names)
|
||||||
|
|
||||||
# Exceptions
|
# Exceptions
|
||||||
for future in self._future_exceptions:
|
for future in self._future_exceptions:
|
||||||
|
@ -158,6 +168,3 @@ class Blueprint(
|
||||||
# Event listeners
|
# Event listeners
|
||||||
for listener in self._future_listeners:
|
for listener in self._future_listeners:
|
||||||
app._apply_listener(listener)
|
app._apply_listener(listener)
|
||||||
|
|
||||||
def _generate_name(self, handler, name: str) -> str:
|
|
||||||
return f"{self.name}.{name or handler.__name__}"
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
class Base(type):
|
|
||||||
def __new__(cls, name, bases, attrs):
|
|
||||||
init = attrs.get("__init__")
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
nonlocal init
|
|
||||||
for base in type(self).__bases__:
|
|
||||||
if base.__name__ != "BaseMixin":
|
|
||||||
base.__init__(self, *args, **kwargs)
|
|
||||||
|
|
||||||
if init:
|
|
||||||
init(self, *args, **kwargs)
|
|
||||||
|
|
||||||
attrs["__init__"] = __init__
|
|
||||||
return type.__new__(cls, name, bases, attrs)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMixin(metaclass=Base):
|
|
||||||
...
|
|
|
@ -1,5 +1,3 @@
|
||||||
from enum import Enum, auto
|
|
||||||
from functools import partial
|
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
from sanic.models.futures import FutureException
|
from sanic.models.futures import FutureException
|
||||||
|
@ -29,6 +27,9 @@ class ExceptionMixin:
|
||||||
nonlocal apply
|
nonlocal apply
|
||||||
nonlocal exceptions
|
nonlocal exceptions
|
||||||
|
|
||||||
|
if isinstance(exceptions[0], list):
|
||||||
|
exceptions = tuple(*exceptions)
|
||||||
|
|
||||||
future_exception = FutureException(handler, exceptions)
|
future_exception = FutureException(handler, exceptions)
|
||||||
self._future_exceptions.add(future_exception)
|
self._future_exceptions.add(future_exception)
|
||||||
if apply:
|
if apply:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, Callable, Coroutine, Optional, Set, Union
|
from typing import Any, Callable, Coroutine, List, Optional, Set, Union
|
||||||
|
|
||||||
from sanic.models.futures import FutureListener
|
from sanic.models.futures import FutureListener
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ class ListenerEvent(str, Enum):
|
||||||
|
|
||||||
class ListenerMixin:
|
class ListenerMixin:
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
self._future_listeners: Set[FutureListener] = set()
|
self._future_listeners: List[FutureListener] = list()
|
||||||
|
|
||||||
def _apply_listener(self, listener: FutureListener):
|
def _apply_listener(self, listener: FutureListener):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -51,7 +51,7 @@ class ListenerMixin:
|
||||||
nonlocal apply
|
nonlocal apply
|
||||||
|
|
||||||
future_listener = FutureListener(listener, event)
|
future_listener = FutureListener(listener, event)
|
||||||
self._future_listeners.add(future_listener)
|
self._future_listeners.append(future_listener)
|
||||||
if apply:
|
if apply:
|
||||||
self._apply_listener(future_listener)
|
self._apply_listener(future_listener)
|
||||||
return listener
|
return listener
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Set
|
from typing import List
|
||||||
|
|
||||||
from sanic.models.futures import FutureMiddleware
|
from sanic.models.futures import FutureMiddleware
|
||||||
|
|
||||||
|
|
||||||
class MiddlewareMixin:
|
class MiddlewareMixin:
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
self._future_middleware: Set[FutureMiddleware] = set()
|
self._future_middleware: List[FutureMiddleware] = list()
|
||||||
|
|
||||||
def _apply_middleware(self, middleware: FutureMiddleware):
|
def _apply_middleware(self, middleware: FutureMiddleware):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -30,7 +30,7 @@ class MiddlewareMixin:
|
||||||
nonlocal apply
|
nonlocal apply
|
||||||
|
|
||||||
future_middleware = FutureMiddleware(middleware, attach_to)
|
future_middleware = FutureMiddleware(middleware, attach_to)
|
||||||
self._future_middleware.add(future_middleware)
|
self._future_middleware.append(future_middleware)
|
||||||
if apply:
|
if apply:
|
||||||
self._apply_middleware(future_middleware)
|
self._apply_middleware(future_middleware)
|
||||||
return middleware
|
return middleware
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
from functools import partial
|
|
||||||
from inspect import signature
|
from inspect import signature
|
||||||
from pathlib import PurePath
|
from pathlib import PurePath
|
||||||
from typing import Iterable, List, Optional, Set, Union
|
from typing import Iterable, List, Optional, Set, Union
|
||||||
|
|
||||||
from sanic_routing.route import Route
|
from sanic_routing.route import Route # type: ignore
|
||||||
|
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.models.futures import FutureRoute, FutureStatic
|
from sanic.models.futures import FutureRoute, FutureStatic
|
||||||
|
@ -36,6 +35,8 @@ class RouteMixin:
|
||||||
apply: bool = True,
|
apply: bool = True,
|
||||||
subprotocols: Optional[List[str]] = None,
|
subprotocols: Optional[List[str]] = None,
|
||||||
websocket: bool = False,
|
websocket: bool = False,
|
||||||
|
unquote: bool = False,
|
||||||
|
static: bool = False,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Decorate a function to be registered as a route
|
Decorate a function to be registered as a route
|
||||||
|
@ -52,9 +53,6 @@ class RouteMixin:
|
||||||
:return: tuple of routes, decorated function
|
:return: tuple of routes, decorated function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if websocket:
|
|
||||||
self.enable_websocket()
|
|
||||||
|
|
||||||
# Fix case where the user did not prefix the URL with a /
|
# Fix case where the user did not prefix the URL with a /
|
||||||
# and will probably get confused as to why it's not working
|
# and will probably get confused as to why it's not working
|
||||||
if not uri.startswith("/"):
|
if not uri.startswith("/"):
|
||||||
|
@ -63,6 +61,9 @@ class RouteMixin:
|
||||||
if strict_slashes is None:
|
if strict_slashes is None:
|
||||||
strict_slashes = self.strict_slashes
|
strict_slashes = self.strict_slashes
|
||||||
|
|
||||||
|
if not methods and not websocket:
|
||||||
|
methods = frozenset({"GET"})
|
||||||
|
|
||||||
def decorator(handler):
|
def decorator(handler):
|
||||||
nonlocal uri
|
nonlocal uri
|
||||||
nonlocal methods
|
nonlocal methods
|
||||||
|
@ -74,39 +75,43 @@ class RouteMixin:
|
||||||
nonlocal ignore_body
|
nonlocal ignore_body
|
||||||
nonlocal subprotocols
|
nonlocal subprotocols
|
||||||
nonlocal websocket
|
nonlocal websocket
|
||||||
|
nonlocal static
|
||||||
|
|
||||||
if isinstance(handler, tuple):
|
if isinstance(handler, tuple):
|
||||||
# if a handler fn is already wrapped in a route, the handler
|
# if a handler fn is already wrapped in a route, the handler
|
||||||
# variable will be a tuple of (existing routes, handler fn)
|
# variable will be a tuple of (existing routes, handler fn)
|
||||||
_, handler = handler
|
_, handler = handler
|
||||||
|
|
||||||
if websocket:
|
name = self._generate_name(name, handler)
|
||||||
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:
|
if isinstance(host, str):
|
||||||
# - THink this thru.... do we want all routes namespaced?
|
host = frozenset([host])
|
||||||
# -
|
elif host and not isinstance(host, frozenset):
|
||||||
name = self._generate_name(handler, name)
|
try:
|
||||||
|
host = frozenset(host)
|
||||||
|
except TypeError:
|
||||||
|
raise ValueError(
|
||||||
|
"Expected either string or Iterable of host strings, "
|
||||||
|
"not %s" % host
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(subprotocols, (list, tuple, set)):
|
||||||
|
subprotocols = frozenset(subprotocols)
|
||||||
|
|
||||||
route = FutureRoute(
|
route = FutureRoute(
|
||||||
handler,
|
handler,
|
||||||
uri,
|
uri,
|
||||||
frozenset(methods),
|
None if websocket else frozenset([x.upper() for x in methods]),
|
||||||
host,
|
host,
|
||||||
strict_slashes,
|
strict_slashes,
|
||||||
stream,
|
stream,
|
||||||
version,
|
version,
|
||||||
name,
|
name,
|
||||||
ignore_body,
|
ignore_body,
|
||||||
|
websocket,
|
||||||
|
subprotocols,
|
||||||
|
unquote,
|
||||||
|
static,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._future_routes.add(route)
|
self._future_routes.add(route)
|
||||||
|
@ -441,6 +446,7 @@ class RouteMixin:
|
||||||
subprotocols: Optional[List[str]] = None,
|
subprotocols: Optional[List[str]] = None,
|
||||||
version: Optional[int] = None,
|
version: Optional[int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
|
apply: bool = True,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Decorate a function to be registered as a websocket route
|
Decorate a function to be registered as a websocket route
|
||||||
|
@ -543,12 +549,16 @@ class RouteMixin:
|
||||||
:rtype: List[sanic.router.Route]
|
:rtype: List[sanic.router.Route]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not name.startswith(self.name + "."):
|
name = self._generate_name(name)
|
||||||
name = f"{self.name}.{name}"
|
|
||||||
|
|
||||||
if strict_slashes is None and self.strict_slashes is not None:
|
if strict_slashes is None and self.strict_slashes is not None:
|
||||||
strict_slashes = self.strict_slashes
|
strict_slashes = self.strict_slashes
|
||||||
|
|
||||||
|
if not isinstance(file_or_directory, (str, bytes, PurePath)):
|
||||||
|
raise ValueError(
|
||||||
|
f"Static route must be a valid path, not {file_or_directory}"
|
||||||
|
)
|
||||||
|
|
||||||
static = FutureStatic(
|
static = FutureStatic(
|
||||||
uri,
|
uri,
|
||||||
file_or_directory,
|
file_or_directory,
|
||||||
|
@ -566,5 +576,29 @@ class RouteMixin:
|
||||||
if apply:
|
if apply:
|
||||||
self._apply_static(static)
|
self._apply_static(static)
|
||||||
|
|
||||||
def _generate_name(self, handler, name: str) -> str:
|
def _generate_name(self, *objects) -> str:
|
||||||
return name or handler.__name__
|
name = None
|
||||||
|
|
||||||
|
for obj in objects:
|
||||||
|
if obj:
|
||||||
|
if isinstance(obj, str):
|
||||||
|
name = obj
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
name = obj.name
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
name = obj.__name__
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
raise Exception("...")
|
||||||
|
|
||||||
|
if not name.startswith(f"{self.name}."):
|
||||||
|
name = f"{self.name}.{name}"
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
|
@ -13,6 +13,10 @@ FutureRoute = namedtuple(
|
||||||
"version",
|
"version",
|
||||||
"name",
|
"name",
|
||||||
"ignore_body",
|
"ignore_body",
|
||||||
|
"websocket",
|
||||||
|
"subprotocols",
|
||||||
|
"unquote",
|
||||||
|
"static",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
FutureListener = namedtuple("FutureListener", ["listener", "event"])
|
FutureListener = namedtuple("FutureListener", ["listener", "event"])
|
||||||
|
|
|
@ -87,6 +87,7 @@ class Request:
|
||||||
"_port",
|
"_port",
|
||||||
"_remote_addr",
|
"_remote_addr",
|
||||||
"_socket",
|
"_socket",
|
||||||
|
"_match_info",
|
||||||
"app",
|
"app",
|
||||||
"body",
|
"body",
|
||||||
"conn_info",
|
"conn_info",
|
||||||
|
@ -147,6 +148,7 @@ class Request:
|
||||||
self.uri_template: Optional[str] = None
|
self.uri_template: Optional[str] = None
|
||||||
self.request_middleware_started = False
|
self.request_middleware_started = False
|
||||||
self._cookies: Dict[str, str] = {}
|
self._cookies: Dict[str, str] = {}
|
||||||
|
self._match_info = {}
|
||||||
self.stream: Optional[Http] = None
|
self.stream: Optional[Http] = None
|
||||||
self.endpoint: Optional[str] = None
|
self.endpoint: Optional[str] = None
|
||||||
|
|
||||||
|
@ -455,7 +457,7 @@ class Request:
|
||||||
"""
|
"""
|
||||||
:return: matched info after resolving route
|
:return: matched info after resolving route
|
||||||
"""
|
"""
|
||||||
return self.app.router.get(self)[2]
|
return self._match_info
|
||||||
|
|
||||||
# Transport properties (obtained from local interface only)
|
# Transport properties (obtained from local interface only)
|
||||||
|
|
||||||
|
|
175
sanic/router.py
175
sanic/router.py
|
@ -1,14 +1,22 @@
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from typing import Any, Dict, Iterable, Optional, Tuple, Union
|
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
|
||||||
|
|
||||||
from sanic_routing import BaseRouter
|
from sanic_routing import BaseRouter # type: ignore
|
||||||
from sanic_routing.route import Route
|
from sanic_routing.exceptions import NoMethod # type: ignore
|
||||||
|
from sanic_routing.exceptions import (
|
||||||
|
NotFound as RoutingNotFound, # type: ignore
|
||||||
|
)
|
||||||
|
from sanic_routing.route import Route # type: ignore
|
||||||
|
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
|
from sanic.exceptions import MethodNotSupported, NotFound
|
||||||
from sanic.handlers import RouteHandler
|
from sanic.handlers import RouteHandler
|
||||||
from sanic.request import Request
|
from sanic.request import Request
|
||||||
|
|
||||||
|
|
||||||
|
ROUTER_CACHE_SIZE = 1024
|
||||||
|
|
||||||
|
|
||||||
class Router(BaseRouter):
|
class Router(BaseRouter):
|
||||||
"""
|
"""
|
||||||
The router implementation responsible for routing a :class:`Request` object
|
The router implementation responsible for routing a :class:`Request` object
|
||||||
|
@ -18,18 +26,38 @@ class Router(BaseRouter):
|
||||||
DEFAULT_METHOD = "GET"
|
DEFAULT_METHOD = "GET"
|
||||||
ALLOWED_METHODS = HTTP_METHODS
|
ALLOWED_METHODS = HTTP_METHODS
|
||||||
|
|
||||||
@lru_cache
|
# Putting the lru_cache on Router.get() performs better for the benchmarsk
|
||||||
def get(
|
# at tests/benchmark/test_route_resolution_benchmark.py
|
||||||
self, request: Request
|
# However, overall application performance is significantly improved
|
||||||
) -> Tuple[
|
# with the lru_cache on this method.
|
||||||
RouteHandler,
|
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
||||||
Tuple[Any, ...],
|
def _get(
|
||||||
Dict[str, Any],
|
self, path, method, host
|
||||||
str,
|
) -> Tuple[RouteHandler, Dict[str, Any], str, str, bool,]:
|
||||||
str,
|
try:
|
||||||
Optional[str],
|
route, handler, params = self.resolve(
|
||||||
bool,
|
path=path,
|
||||||
]:
|
method=method,
|
||||||
|
extra={"host": host},
|
||||||
|
)
|
||||||
|
except RoutingNotFound as e:
|
||||||
|
raise NotFound("Requested URL {} not found".format(e.path))
|
||||||
|
except NoMethod as e:
|
||||||
|
raise MethodNotSupported(
|
||||||
|
"Method {} not allowed for URL {}".format(method, path),
|
||||||
|
method=method,
|
||||||
|
allowed_methods=e.allowed_methods,
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
handler,
|
||||||
|
params,
|
||||||
|
route.path,
|
||||||
|
route.name,
|
||||||
|
route.ctx.ignore_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get(self, request: Request):
|
||||||
"""
|
"""
|
||||||
Retrieve a `Route` object containg the details about how to handle
|
Retrieve a `Route` object containg the details about how to handle
|
||||||
a response for a given request
|
a response for a given request
|
||||||
|
@ -41,23 +69,8 @@ class Router(BaseRouter):
|
||||||
:rtype: Tuple[ RouteHandler, Tuple[Any, ...], Dict[str, Any], str, str,
|
:rtype: Tuple[ RouteHandler, Tuple[Any, ...], Dict[str, Any], str, str,
|
||||||
Optional[str], bool, ]
|
Optional[str], bool, ]
|
||||||
"""
|
"""
|
||||||
route, handler, params = self.resolve(
|
return self._get(
|
||||||
path=request.path,
|
request.path, request.method, request.headers.get("host")
|
||||||
method=request.method,
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: Implement response
|
|
||||||
# - args,
|
|
||||||
# - endpoint,
|
|
||||||
|
|
||||||
return (
|
|
||||||
handler,
|
|
||||||
(),
|
|
||||||
params,
|
|
||||||
route.path,
|
|
||||||
route.name,
|
|
||||||
None,
|
|
||||||
route.ctx.ignore_body,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def add(
|
def add(
|
||||||
|
@ -65,13 +78,15 @@ class Router(BaseRouter):
|
||||||
uri: str,
|
uri: str,
|
||||||
methods: Iterable[str],
|
methods: Iterable[str],
|
||||||
handler: RouteHandler,
|
handler: RouteHandler,
|
||||||
host: Optional[str] = None,
|
host: Optional[Union[str, Iterable[str]]] = None,
|
||||||
strict_slashes: bool = False,
|
strict_slashes: bool = False,
|
||||||
stream: bool = False,
|
stream: bool = False,
|
||||||
ignore_body: bool = False,
|
ignore_body: bool = False,
|
||||||
version: Union[str, float, int] = None,
|
version: Union[str, float, int] = None,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
) -> Route:
|
unquote: bool = False,
|
||||||
|
static: bool = False,
|
||||||
|
) -> Union[Route, List[Route]]:
|
||||||
"""
|
"""
|
||||||
Add a handler to the router
|
Add a handler to the router
|
||||||
|
|
||||||
|
@ -99,19 +114,93 @@ class Router(BaseRouter):
|
||||||
:return: the route object
|
:return: the route object
|
||||||
:rtype: Route
|
:rtype: Route
|
||||||
"""
|
"""
|
||||||
# TODO: Implement
|
|
||||||
# - host
|
|
||||||
# - strict_slashes
|
|
||||||
# - 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("/")])
|
||||||
|
|
||||||
route = super().add(
|
params = dict(
|
||||||
path=uri, handler=handler, methods=methods, name=name
|
path=uri,
|
||||||
|
handler=handler,
|
||||||
|
methods=methods,
|
||||||
|
name=name,
|
||||||
|
strict=strict_slashes,
|
||||||
|
unquote=unquote,
|
||||||
)
|
)
|
||||||
route.ctx.ignore_body = ignore_body
|
|
||||||
route.ctx.stream = stream
|
if isinstance(host, str):
|
||||||
|
hosts = [host]
|
||||||
|
else:
|
||||||
|
hosts = host or [None] # type: ignore
|
||||||
|
|
||||||
|
routes = []
|
||||||
|
|
||||||
|
for host in hosts:
|
||||||
|
if host:
|
||||||
|
params.update({"requirements": {"host": host}})
|
||||||
|
|
||||||
|
route = super().add(**params)
|
||||||
|
route.ctx.ignore_body = ignore_body
|
||||||
|
route.ctx.stream = stream
|
||||||
|
route.ctx.hosts = hosts
|
||||||
|
route.ctx.static = static
|
||||||
|
|
||||||
|
routes.append(route)
|
||||||
|
|
||||||
|
if len(routes) == 1:
|
||||||
|
return routes[0]
|
||||||
|
return routes
|
||||||
|
|
||||||
|
def is_stream_handler(self, request) -> bool:
|
||||||
|
"""
|
||||||
|
Handler for request is stream or not.
|
||||||
|
|
||||||
|
:param request: Request object
|
||||||
|
:return: bool
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
handler = self.get(request)[0]
|
||||||
|
except (NotFound, MethodNotSupported):
|
||||||
|
return False
|
||||||
|
if hasattr(handler, "view_class") and hasattr(
|
||||||
|
handler.view_class, request.method.lower()
|
||||||
|
):
|
||||||
|
handler = getattr(handler.view_class, request.method.lower())
|
||||||
|
return hasattr(handler, "is_stream")
|
||||||
|
|
||||||
|
@lru_cache(maxsize=ROUTER_CACHE_SIZE)
|
||||||
|
def find_route_by_view_name(self, view_name, name=None):
|
||||||
|
"""
|
||||||
|
Find a route in the router based on the specified view name.
|
||||||
|
|
||||||
|
:param view_name: string of view name to search by
|
||||||
|
:param kwargs: additional params, usually for static files
|
||||||
|
:return: tuple containing (uri, Route)
|
||||||
|
"""
|
||||||
|
if not view_name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
route = self.name_index.get(view_name)
|
||||||
|
if not route:
|
||||||
|
full_name = self.ctx.app._generate_name(view_name)
|
||||||
|
route = self.name_index.get(full_name)
|
||||||
|
|
||||||
|
if not route:
|
||||||
|
return None
|
||||||
|
|
||||||
return route
|
return route
|
||||||
|
|
||||||
|
@property
|
||||||
|
def routes_all(self):
|
||||||
|
return self.routes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def routes_static(self):
|
||||||
|
return self.static_routes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def routes_dynamic(self):
|
||||||
|
return self.dynamic_routes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def routes_regex(self):
|
||||||
|
return self.regex_routes
|
||||||
|
|
|
@ -157,11 +157,11 @@ def register(
|
||||||
# If we're not trying to match a file directly,
|
# If we're not trying to match a file directly,
|
||||||
# serve from the folder
|
# serve from the folder
|
||||||
if not path.isfile(file_or_directory):
|
if not path.isfile(file_or_directory):
|
||||||
uri += "<file_uri:" + static.pattern + ">"
|
uri += "/<file_uri>"
|
||||||
|
|
||||||
# special prefix for static files
|
# special prefix for static files
|
||||||
if not static.name.startswith("_static_"):
|
# if not static.name.startswith("_static_"):
|
||||||
name = f"_static_{static.name}"
|
# name = f"_static_{static.name}"
|
||||||
|
|
||||||
_handler = wraps(_static_request_handler)(
|
_handler = wraps(_static_request_handler)(
|
||||||
partial(
|
partial(
|
||||||
|
@ -174,11 +174,13 @@ def register(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
_routes, _ = app.route(
|
route, _ = app.route(
|
||||||
uri=uri,
|
uri=uri,
|
||||||
methods=["GET", "HEAD"],
|
methods=["GET", "HEAD"],
|
||||||
name=name,
|
name=name,
|
||||||
host=static.host,
|
host=static.host,
|
||||||
strict_slashes=static.strict_slashes,
|
strict_slashes=static.strict_slashes,
|
||||||
|
static=True,
|
||||||
)(_handler)
|
)(_handler)
|
||||||
return _routes
|
|
||||||
|
return route
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -83,6 +83,7 @@ ujson = "ujson>=1.35" + env_dependency
|
||||||
uvloop = "uvloop>=0.5.3" + env_dependency
|
uvloop = "uvloop>=0.5.3" + env_dependency
|
||||||
|
|
||||||
requirements = [
|
requirements = [
|
||||||
|
"sanic-routing",
|
||||||
"httptools>=0.0.10",
|
"httptools>=0.0.10",
|
||||||
uvloop,
|
uvloop,
|
||||||
ujson,
|
ujson,
|
||||||
|
|
|
@ -4,6 +4,8 @@ from pytest import mark
|
||||||
|
|
||||||
import sanic.router
|
import sanic.router
|
||||||
|
|
||||||
|
from sanic.request import Request
|
||||||
|
|
||||||
|
|
||||||
seed("Pack my box with five dozen liquor jugs.")
|
seed("Pack my box with five dozen liquor jugs.")
|
||||||
|
|
||||||
|
@ -23,8 +25,17 @@ class TestSanicRouteResolution:
|
||||||
route_to_call = choice(simple_routes)
|
route_to_call = choice(simple_routes)
|
||||||
|
|
||||||
result = benchmark.pedantic(
|
result = benchmark.pedantic(
|
||||||
router._get,
|
router.get,
|
||||||
("/{}".format(route_to_call[-1]), route_to_call[0], "localhost"),
|
(
|
||||||
|
Request(
|
||||||
|
"/{}".format(route_to_call[-1]).encode(),
|
||||||
|
{"host": "localhost"},
|
||||||
|
"v1",
|
||||||
|
route_to_call[0],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
),
|
||||||
iterations=1000,
|
iterations=1000,
|
||||||
rounds=1000,
|
rounds=1000,
|
||||||
)
|
)
|
||||||
|
@ -47,8 +58,17 @@ class TestSanicRouteResolution:
|
||||||
print("{} -> {}".format(route_to_call[-1], url))
|
print("{} -> {}".format(route_to_call[-1], url))
|
||||||
|
|
||||||
result = benchmark.pedantic(
|
result = benchmark.pedantic(
|
||||||
router._get,
|
router.get,
|
||||||
("/{}".format(url), route_to_call[0], "localhost"),
|
(
|
||||||
|
Request(
|
||||||
|
"/{}".format(url).encode(),
|
||||||
|
{"host": "localhost"},
|
||||||
|
"v1",
|
||||||
|
route_to_call[0],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
),
|
||||||
iterations=1000,
|
iterations=1000,
|
||||||
rounds=1000,
|
rounds=1000,
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,12 +4,16 @@ import string
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from sanic_routing.exceptions import RouteExists
|
||||||
from sanic_testing import TestManager
|
from sanic_testing import TestManager
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.router import RouteExists, Router
|
from sanic.constants import HTTP_METHODS
|
||||||
|
from sanic.router import Router
|
||||||
|
|
||||||
|
|
||||||
random.seed("Pack my box with five dozen liquor jugs.")
|
random.seed("Pack my box with five dozen liquor jugs.")
|
||||||
|
@ -38,12 +42,12 @@ async def _handler(request):
|
||||||
|
|
||||||
TYPE_TO_GENERATOR_MAP = {
|
TYPE_TO_GENERATOR_MAP = {
|
||||||
"string": lambda: "".join(
|
"string": lambda: "".join(
|
||||||
[random.choice(string.ascii_letters + string.digits) for _ in range(4)]
|
[random.choice(string.ascii_lowercase) for _ in range(4)]
|
||||||
),
|
),
|
||||||
"int": lambda: random.choice(range(1000000)),
|
"int": lambda: random.choice(range(1000000)),
|
||||||
"number": lambda: random.random(),
|
"number": lambda: random.random(),
|
||||||
"alpha": lambda: "".join(
|
"alpha": lambda: "".join(
|
||||||
[random.choice(string.ascii_letters) for _ in range(4)]
|
[random.choice(string.ascii_lowercase) for _ in range(4)]
|
||||||
),
|
),
|
||||||
"uuid": lambda: str(uuid.uuid1()),
|
"uuid": lambda: str(uuid.uuid1()),
|
||||||
}
|
}
|
||||||
|
@ -52,7 +56,7 @@ TYPE_TO_GENERATOR_MAP = {
|
||||||
class RouteStringGenerator:
|
class RouteStringGenerator:
|
||||||
|
|
||||||
ROUTE_COUNT_PER_DEPTH = 100
|
ROUTE_COUNT_PER_DEPTH = 100
|
||||||
HTTP_METHODS = ["GET", "PUT", "POST", "PATCH", "DELETE", "OPTION"]
|
HTTP_METHODS = HTTP_METHODS
|
||||||
ROUTE_PARAM_TYPES = ["string", "int", "number", "alpha", "uuid"]
|
ROUTE_PARAM_TYPES = ["string", "int", "number", "alpha", "uuid"]
|
||||||
|
|
||||||
def generate_random_direct_route(self, max_route_depth=4):
|
def generate_random_direct_route(self, max_route_depth=4):
|
||||||
|
@ -105,12 +109,12 @@ class RouteStringGenerator:
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def sanic_router(app):
|
def sanic_router(app):
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
def _setup(route_details: tuple) -> (Router, tuple):
|
def _setup(route_details: tuple) -> Tuple[Router, tuple]:
|
||||||
router = Router(app)
|
router = Router()
|
||||||
added_router = []
|
added_router = []
|
||||||
for method, route in route_details:
|
for method, route in route_details:
|
||||||
try:
|
try:
|
||||||
router._add(
|
router.add(
|
||||||
uri=f"/{route}",
|
uri=f"/{route}",
|
||||||
methods=frozenset({method}),
|
methods=frozenset({method}),
|
||||||
host="localhost",
|
host="localhost",
|
||||||
|
@ -119,6 +123,7 @@ def sanic_router(app):
|
||||||
added_router.append((method, route))
|
added_router.append((method, route))
|
||||||
except RouteExists:
|
except RouteExists:
|
||||||
pass
|
pass
|
||||||
|
router.finalize()
|
||||||
return router, added_router
|
return router, added_router
|
||||||
|
|
||||||
return _setup
|
return _setup
|
||||||
|
@ -137,5 +142,4 @@ def url_param_generator():
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def app(request):
|
def app(request):
|
||||||
app = Sanic(request.node.name)
|
app = Sanic(request.node.name)
|
||||||
# TestManager(app)
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -118,7 +118,7 @@ def test_app_route_raise_value_error(app):
|
||||||
|
|
||||||
def test_app_handle_request_handler_is_none(app, monkeypatch):
|
def test_app_handle_request_handler_is_none(app, monkeypatch):
|
||||||
def mockreturn(*args, **kwargs):
|
def mockreturn(*args, **kwargs):
|
||||||
return None, [], {}, "", "", None, False
|
return None, {}, "", "", False
|
||||||
|
|
||||||
# Not sure how to make app.router.get() return None, so use mock here.
|
# Not sure how to make app.router.get() return None, so use mock here.
|
||||||
monkeypatch.setattr(app.router, "get", mockreturn)
|
monkeypatch.setattr(app.router, "get", mockreturn)
|
||||||
|
|
|
@ -45,7 +45,8 @@ def protocol(transport):
|
||||||
return transport.get_protocol()
|
return transport.get_protocol()
|
||||||
|
|
||||||
|
|
||||||
def test_listeners_triggered(app):
|
def test_listeners_triggered():
|
||||||
|
app = Sanic("app")
|
||||||
before_server_start = False
|
before_server_start = False
|
||||||
after_server_start = False
|
after_server_start = False
|
||||||
before_server_stop = False
|
before_server_stop = False
|
||||||
|
@ -71,6 +72,10 @@ def test_listeners_triggered(app):
|
||||||
nonlocal after_server_stop
|
nonlocal after_server_stop
|
||||||
after_server_stop = True
|
after_server_stop = True
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def handler(request):
|
||||||
|
return text("...")
|
||||||
|
|
||||||
class CustomServer(uvicorn.Server):
|
class CustomServer(uvicorn.Server):
|
||||||
def install_signal_handlers(self):
|
def install_signal_handlers(self):
|
||||||
pass
|
pass
|
||||||
|
@ -121,6 +126,10 @@ def test_listeners_triggered_async(app):
|
||||||
nonlocal after_server_stop
|
nonlocal after_server_stop
|
||||||
after_server_stop = True
|
after_server_stop = True
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def handler(request):
|
||||||
|
return text("...")
|
||||||
|
|
||||||
class CustomServer(uvicorn.Server):
|
class CustomServer(uvicorn.Server):
|
||||||
def install_signal_handlers(self):
|
def install_signal_handlers(self):
|
||||||
pass
|
pass
|
||||||
|
@ -325,7 +334,7 @@ async def test_cookie_customization(app):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_json_content_type(app):
|
async def test_content_type(app):
|
||||||
@app.get("/json")
|
@app.get("/json")
|
||||||
def send_json(request):
|
def send_json(request):
|
||||||
return json({"foo": "bar"})
|
return json({"foo": "bar"})
|
||||||
|
|
|
@ -4,6 +4,8 @@ import asyncio
|
||||||
def test_bad_request_response(app):
|
def test_bad_request_response(app):
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
|
app.get("/")(lambda x: ...)
|
||||||
|
|
||||||
@app.listener("after_server_start")
|
@app.listener("after_server_start")
|
||||||
async def _request(sanic, loop):
|
async def _request(sanic, loop):
|
||||||
connect = asyncio.open_connection("127.0.0.1", 42101)
|
connect = asyncio.open_connection("127.0.0.1", 42101)
|
||||||
|
|
|
@ -209,18 +209,28 @@ def test_bp_with_host(app):
|
||||||
|
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
headers = {"Host": "example.com"}
|
headers = {"Host": "example.com"}
|
||||||
|
|
||||||
request, response = app.test_client.get("/test1/", headers=headers)
|
request, response = app.test_client.get("/test1/", headers=headers)
|
||||||
assert response.text == "Hello"
|
assert response.text == "Hello"
|
||||||
|
|
||||||
headers = {"Host": "sub.example.com"}
|
headers = {"Host": "sub.example.com"}
|
||||||
request, response = app.test_client.get("/test1/", headers=headers)
|
request, response = app.test_client.get("/test1/", headers=headers)
|
||||||
|
assert response.body == b"Hello subdomain!"
|
||||||
assert response.text == "Hello subdomain!"
|
|
||||||
|
|
||||||
|
|
||||||
def test_several_bp_with_host(app):
|
def test_several_bp_with_host(app):
|
||||||
bp = Blueprint("test_text", url_prefix="/test", host="example.com")
|
bp = Blueprint(
|
||||||
bp2 = Blueprint("test_text2", url_prefix="/test", host="sub.example.com")
|
"test_text",
|
||||||
|
url_prefix="/test",
|
||||||
|
host="example.com",
|
||||||
|
strict_slashes=True,
|
||||||
|
)
|
||||||
|
bp2 = Blueprint(
|
||||||
|
"test_text2",
|
||||||
|
url_prefix="/test",
|
||||||
|
host="sub.example.com",
|
||||||
|
strict_slashes=True,
|
||||||
|
)
|
||||||
|
|
||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
|
@ -240,6 +250,7 @@ def test_several_bp_with_host(app):
|
||||||
assert bp.host == "example.com"
|
assert bp.host == "example.com"
|
||||||
headers = {"Host": "example.com"}
|
headers = {"Host": "example.com"}
|
||||||
request, response = app.test_client.get("/test/", headers=headers)
|
request, response = app.test_client.get("/test/", headers=headers)
|
||||||
|
|
||||||
assert response.text == "Hello"
|
assert response.text == "Hello"
|
||||||
|
|
||||||
assert bp2.host == "sub.example.com"
|
assert bp2.host == "sub.example.com"
|
||||||
|
@ -352,6 +363,29 @@ def test_bp_middleware(app):
|
||||||
assert response.text == "FAIL"
|
assert response.text == "FAIL"
|
||||||
|
|
||||||
|
|
||||||
|
def test_bp_middleware_with_route(app):
|
||||||
|
blueprint = Blueprint("test_bp_middleware")
|
||||||
|
|
||||||
|
@blueprint.middleware("response")
|
||||||
|
async def process_response(request, response):
|
||||||
|
return text("OK")
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
async def handler(request):
|
||||||
|
return text("FAIL")
|
||||||
|
|
||||||
|
@blueprint.route("/bp")
|
||||||
|
async def bp_handler(request):
|
||||||
|
return text("FAIL")
|
||||||
|
|
||||||
|
app.blueprint(blueprint)
|
||||||
|
|
||||||
|
request, response = app.test_client.get("/bp")
|
||||||
|
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == "OK"
|
||||||
|
|
||||||
|
|
||||||
def test_bp_middleware_order(app):
|
def test_bp_middleware_order(app):
|
||||||
blueprint = Blueprint("test_bp_middleware_order")
|
blueprint = Blueprint("test_bp_middleware_order")
|
||||||
order = list()
|
order = list()
|
||||||
|
@ -425,6 +459,7 @@ def test_bp_exception_handler(app):
|
||||||
|
|
||||||
|
|
||||||
def test_bp_listeners(app):
|
def test_bp_listeners(app):
|
||||||
|
app.route("/")(lambda x: x)
|
||||||
blueprint = Blueprint("test_middleware")
|
blueprint = Blueprint("test_middleware")
|
||||||
|
|
||||||
order = []
|
order = []
|
||||||
|
@ -537,19 +572,19 @@ def test_bp_shorthand(app):
|
||||||
app.blueprint(blueprint)
|
app.blueprint(blueprint)
|
||||||
|
|
||||||
request, response = app.test_client.get("/get")
|
request, response = app.test_client.get("/get")
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
request, response = app.test_client.post("/get")
|
request, response = app.test_client.post("/get")
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
|
||||||
request, response = app.test_client.put("/put")
|
request, response = app.test_client.put("/put")
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
request, response = app.test_client.get("/post")
|
request, response = app.test_client.get("/post")
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
|
||||||
request, response = app.test_client.post("/post")
|
request, response = app.test_client.post("/post")
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
request, response = app.test_client.get("/post")
|
request, response = app.test_client.get("/post")
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
@ -561,19 +596,19 @@ def test_bp_shorthand(app):
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
|
||||||
request, response = app.test_client.options("/options")
|
request, response = app.test_client.options("/options")
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
request, response = app.test_client.get("/options")
|
request, response = app.test_client.get("/options")
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
|
||||||
request, response = app.test_client.patch("/patch")
|
request, response = app.test_client.patch("/patch")
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
request, response = app.test_client.get("/patch")
|
request, response = app.test_client.get("/patch")
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
|
||||||
request, response = app.test_client.delete("/delete")
|
request, response = app.test_client.delete("/delete")
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
request, response = app.test_client.get("/delete")
|
request, response = app.test_client.get("/delete")
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
@ -699,7 +734,8 @@ def test_blueprint_middleware_with_args(app: Sanic):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file"])
|
@pytest.mark.parametrize("file_name", ["test.file"])
|
||||||
def test_static_blueprint_name(app: Sanic, static_file_directory, file_name):
|
def test_static_blueprint_name(static_file_directory, file_name):
|
||||||
|
app = Sanic("app")
|
||||||
current_file = inspect.getfile(inspect.currentframe())
|
current_file = inspect.getfile(inspect.currentframe())
|
||||||
with open(current_file, "rb") as file:
|
with open(current_file, "rb") as file:
|
||||||
file.read()
|
file.read()
|
||||||
|
@ -814,17 +850,19 @@ def test_duplicate_blueprint(app):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_strict_slashes_behavior_adoption(app):
|
def test_strict_slashes_behavior_adoption():
|
||||||
|
app = Sanic("app")
|
||||||
app.strict_slashes = True
|
app.strict_slashes = True
|
||||||
|
bp = Blueprint("bp")
|
||||||
|
bp2 = Blueprint("bp2", strict_slashes=False)
|
||||||
|
|
||||||
@app.get("/test")
|
@app.get("/test")
|
||||||
def handler_test(request):
|
def handler_test(request):
|
||||||
return text("Test")
|
return text("Test")
|
||||||
|
|
||||||
assert app.test_client.get("/test")[1].status == 200
|
@app.get("/f1", strict_slashes=False)
|
||||||
assert app.test_client.get("/test/")[1].status == 404
|
def f1(request):
|
||||||
|
return text("f1")
|
||||||
bp = Blueprint("bp")
|
|
||||||
|
|
||||||
@bp.get("/one", strict_slashes=False)
|
@bp.get("/one", strict_slashes=False)
|
||||||
def one(request):
|
def one(request):
|
||||||
|
@ -834,7 +872,15 @@ def test_strict_slashes_behavior_adoption(app):
|
||||||
def second(request):
|
def second(request):
|
||||||
return text("second")
|
return text("second")
|
||||||
|
|
||||||
|
@bp2.get("/third")
|
||||||
|
def third(request):
|
||||||
|
return text("third")
|
||||||
|
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
|
app.blueprint(bp2)
|
||||||
|
|
||||||
|
assert app.test_client.get("/test")[1].status == 200
|
||||||
|
assert app.test_client.get("/test/")[1].status == 404
|
||||||
|
|
||||||
assert app.test_client.get("/one")[1].status == 200
|
assert app.test_client.get("/one")[1].status == 200
|
||||||
assert app.test_client.get("/one/")[1].status == 200
|
assert app.test_client.get("/one/")[1].status == 200
|
||||||
|
@ -842,19 +888,8 @@ def test_strict_slashes_behavior_adoption(app):
|
||||||
assert app.test_client.get("/second")[1].status == 200
|
assert app.test_client.get("/second")[1].status == 200
|
||||||
assert app.test_client.get("/second/")[1].status == 404
|
assert app.test_client.get("/second/")[1].status == 404
|
||||||
|
|
||||||
bp2 = Blueprint("bp2", strict_slashes=False)
|
|
||||||
|
|
||||||
@bp2.get("/third")
|
|
||||||
def third(request):
|
|
||||||
return text("third")
|
|
||||||
|
|
||||||
app.blueprint(bp2)
|
|
||||||
assert app.test_client.get("/third")[1].status == 200
|
assert app.test_client.get("/third")[1].status == 200
|
||||||
assert app.test_client.get("/third/")[1].status == 200
|
assert app.test_client.get("/third/")[1].status == 200
|
||||||
|
|
||||||
@app.get("/f1", strict_slashes=False)
|
|
||||||
def f1(request):
|
|
||||||
return text("f1")
|
|
||||||
|
|
||||||
assert app.test_client.get("/f1")[1].status == 200
|
assert app.test_client.get("/f1")[1].status == 200
|
||||||
assert app.test_client.get("/f1/")[1].status == 200
|
assert app.test_client.get("/f1/")[1].status == 200
|
||||||
|
|
|
@ -43,7 +43,7 @@ async def test_cookies_asgi(app):
|
||||||
response_cookies = SimpleCookie()
|
response_cookies = SimpleCookie()
|
||||||
response_cookies.load(response.headers.get("set-cookie", {}))
|
response_cookies.load(response.headers.get("set-cookie", {}))
|
||||||
|
|
||||||
assert response.text == "Cookies are: working!"
|
assert response.body == b"Cookies are: working!"
|
||||||
assert response_cookies["right_back"].value == "at you"
|
assert response_cookies["right_back"].value == "at you"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,44 +1,44 @@
|
||||||
import pytest
|
# import pytest
|
||||||
|
|
||||||
from sanic.response import text
|
# from sanic.response import text
|
||||||
from sanic.router import RouteExists
|
# from sanic.router import RouteExists
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
# @pytest.mark.parametrize(
|
||||||
"method,attr, expected",
|
# "method,attr, expected",
|
||||||
[
|
# [
|
||||||
("get", "text", "OK1 test"),
|
# ("get", "text", "OK1 test"),
|
||||||
("post", "text", "OK2 test"),
|
# ("post", "text", "OK2 test"),
|
||||||
("put", "text", "OK2 test"),
|
# ("put", "text", "OK2 test"),
|
||||||
("delete", "status", 405),
|
# ("delete", "status", 405),
|
||||||
],
|
# ],
|
||||||
)
|
# )
|
||||||
def test_overload_dynamic_routes(app, method, attr, expected):
|
# def test_overload_dynamic_routes(app, method, attr, expected):
|
||||||
@app.route("/overload/<param>", methods=["GET"])
|
# @app.route("/overload/<param>", methods=["GET"])
|
||||||
async def handler1(request, param):
|
# async def handler1(request, param):
|
||||||
return text("OK1 " + param)
|
# return text("OK1 " + param)
|
||||||
|
|
||||||
@app.route("/overload/<param>", methods=["POST", "PUT"])
|
# @app.route("/overload/<param>", methods=["POST", "PUT"])
|
||||||
async def handler2(request, param):
|
# async def handler2(request, param):
|
||||||
return text("OK2 " + param)
|
# return text("OK2 " + param)
|
||||||
|
|
||||||
request, response = getattr(app.test_client, method)("/overload/test")
|
# request, response = getattr(app.test_client, method)("/overload/test")
|
||||||
assert getattr(response, attr) == expected
|
# assert getattr(response, attr) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_overload_dynamic_routes_exist(app):
|
# def test_overload_dynamic_routes_exist(app):
|
||||||
@app.route("/overload/<param>", methods=["GET"])
|
# @app.route("/overload/<param>", methods=["GET"])
|
||||||
async def handler1(request, param):
|
# async def handler1(request, param):
|
||||||
return text("OK1 " + param)
|
# return text("OK1 " + param)
|
||||||
|
|
||||||
@app.route("/overload/<param>", methods=["POST", "PUT"])
|
# @app.route("/overload/<param>", methods=["POST", "PUT"])
|
||||||
async def handler2(request, param):
|
# async def handler2(request, param):
|
||||||
return text("OK2 " + param)
|
# return text("OK2 " + param)
|
||||||
|
|
||||||
# if this doesn't raise an error, than at least the below should happen:
|
# # if this doesn't raise an error, than at least the below should happen:
|
||||||
# assert response.text == 'Duplicated'
|
# # assert response.text == 'Duplicated'
|
||||||
with pytest.raises(RouteExists):
|
# with pytest.raises(RouteExists):
|
||||||
|
|
||||||
@app.route("/overload/<param>", methods=["PUT", "DELETE"])
|
# @app.route("/overload/<param>", methods=["PUT", "DELETE"])
|
||||||
async def handler3(request, param):
|
# async def handler3(request, param):
|
||||||
return text("Duplicated")
|
# return text("Duplicated")
|
||||||
|
|
|
@ -126,8 +126,9 @@ def test_html_traceback_output_in_debug_mode():
|
||||||
assert response.status == 500
|
assert response.status == 500
|
||||||
soup = BeautifulSoup(response.body, "html.parser")
|
soup = BeautifulSoup(response.body, "html.parser")
|
||||||
html = str(soup)
|
html = str(soup)
|
||||||
|
print(html)
|
||||||
|
|
||||||
assert "response = handler(request, *args, **kwargs)" in html
|
assert "response = handler(request, **kwargs)" in html
|
||||||
assert "handler_4" in html
|
assert "handler_4" in html
|
||||||
assert "foo = bar" in html
|
assert "foo = bar" in html
|
||||||
|
|
||||||
|
@ -151,7 +152,7 @@ def test_chained_exception_handler():
|
||||||
soup = BeautifulSoup(response.body, "html.parser")
|
soup = BeautifulSoup(response.body, "html.parser")
|
||||||
html = str(soup)
|
html = str(soup)
|
||||||
|
|
||||||
assert "response = handler(request, *args, **kwargs)" in html
|
assert "response = handler(request, **kwargs)" in html
|
||||||
assert "handler_6" in html
|
assert "handler_6" in html
|
||||||
assert "foo = 1 / arg" in html
|
assert "foo = 1 / arg" in html
|
||||||
assert "ValueError" in html
|
assert "ValueError" in html
|
||||||
|
|
|
@ -103,7 +103,13 @@ def test_logging_pass_customer_logconfig():
|
||||||
assert fmt._fmt == modified_config["formatters"]["access"]["format"]
|
assert fmt._fmt == modified_config["formatters"]["access"]["format"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("debug", (True, False))
|
@pytest.mark.parametrize(
|
||||||
|
"debug",
|
||||||
|
(
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
),
|
||||||
|
)
|
||||||
def test_log_connection_lost(app, debug, monkeypatch):
|
def test_log_connection_lost(app, debug, monkeypatch):
|
||||||
""" Should not log Connection lost exception on non debug """
|
""" Should not log Connection lost exception on non debug """
|
||||||
stream = StringIO()
|
stream = StringIO()
|
||||||
|
@ -117,7 +123,7 @@ def test_log_connection_lost(app, debug, monkeypatch):
|
||||||
request.transport.close()
|
request.transport.close()
|
||||||
return response
|
return response
|
||||||
|
|
||||||
req, res = app.test_client.get("/conn_lost", debug=debug)
|
req, res = app.test_client.get("/conn_lost", debug=debug, allow_none=True)
|
||||||
assert res is None
|
assert res is None
|
||||||
|
|
||||||
log = stream.getvalue()
|
log = stream.getvalue()
|
||||||
|
|
|
@ -102,6 +102,7 @@ def test_middleware_response_raise_exception(app, caplog):
|
||||||
async def process_response(request, response):
|
async def process_response(request, response):
|
||||||
raise Exception("Exception at response middleware")
|
raise Exception("Exception at response middleware")
|
||||||
|
|
||||||
|
app.route("/")(lambda x: x)
|
||||||
with caplog.at_level(logging.ERROR):
|
with caplog.at_level(logging.ERROR):
|
||||||
reqrequest, response = app.test_client.get("/fail")
|
reqrequest, response = app.test_client.get("/fail")
|
||||||
|
|
||||||
|
@ -129,7 +130,7 @@ def test_middleware_override_request(app):
|
||||||
async def handler(request):
|
async def handler(request):
|
||||||
return text("FAIL")
|
return text("FAIL")
|
||||||
|
|
||||||
response = app.test_client.get("/", gather_request=False)
|
_, response = app.test_client.get("/", gather_request=False)
|
||||||
|
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == "OK"
|
assert response.text == "OK"
|
||||||
|
|
|
@ -68,9 +68,12 @@ def handler(request):
|
||||||
@pytest.mark.parametrize("protocol", [3, 4])
|
@pytest.mark.parametrize("protocol", [3, 4])
|
||||||
def test_pickle_app(app, protocol):
|
def test_pickle_app(app, protocol):
|
||||||
app.route("/")(handler)
|
app.route("/")(handler)
|
||||||
|
app.router.finalize()
|
||||||
|
app.router.reset()
|
||||||
p_app = pickle.dumps(app, protocol=protocol)
|
p_app = pickle.dumps(app, protocol=protocol)
|
||||||
del app
|
del app
|
||||||
up_p_app = pickle.loads(p_app)
|
up_p_app = pickle.loads(p_app)
|
||||||
|
up_p_app.router.finalize()
|
||||||
assert up_p_app
|
assert up_p_app
|
||||||
request, response = up_p_app.test_client.get("/")
|
request, response = up_p_app.test_client.get("/")
|
||||||
assert response.text == "Hello"
|
assert response.text == "Hello"
|
||||||
|
@ -81,9 +84,12 @@ def test_pickle_app_with_bp(app, protocol):
|
||||||
bp = Blueprint("test_text")
|
bp = Blueprint("test_text")
|
||||||
bp.route("/")(handler)
|
bp.route("/")(handler)
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
|
app.router.finalize()
|
||||||
|
app.router.reset()
|
||||||
p_app = pickle.dumps(app, protocol=protocol)
|
p_app = pickle.dumps(app, protocol=protocol)
|
||||||
del app
|
del app
|
||||||
up_p_app = pickle.loads(p_app)
|
up_p_app = pickle.loads(p_app)
|
||||||
|
up_p_app.router.finalize()
|
||||||
assert up_p_app
|
assert up_p_app
|
||||||
request, response = up_p_app.test_client.get("/")
|
request, response = up_p_app.test_client.get("/")
|
||||||
assert response.text == "Hello"
|
assert response.text == "Hello"
|
||||||
|
@ -93,9 +99,12 @@ def test_pickle_app_with_bp(app, protocol):
|
||||||
def test_pickle_app_with_static(app, protocol):
|
def test_pickle_app_with_static(app, protocol):
|
||||||
app.route("/")(handler)
|
app.route("/")(handler)
|
||||||
app.static("/static", "/tmp/static")
|
app.static("/static", "/tmp/static")
|
||||||
|
app.router.finalize()
|
||||||
|
app.router.reset()
|
||||||
p_app = pickle.dumps(app, protocol=protocol)
|
p_app = pickle.dumps(app, protocol=protocol)
|
||||||
del app
|
del app
|
||||||
up_p_app = pickle.loads(p_app)
|
up_p_app = pickle.loads(p_app)
|
||||||
|
up_p_app.router.finalize()
|
||||||
assert up_p_app
|
assert up_p_app
|
||||||
request, response = up_p_app.test_client.get("/static/missing.txt")
|
request, response = up_p_app.test_client.get("/static/missing.txt")
|
||||||
assert response.status == 404
|
assert response.status == 404
|
||||||
|
|
|
@ -5,6 +5,7 @@ import asyncio
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
from sanic.exceptions import URLBuildError
|
from sanic.exceptions import URLBuildError
|
||||||
|
@ -17,7 +18,9 @@ from sanic.response import text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("method", HTTP_METHODS)
|
@pytest.mark.parametrize("method", HTTP_METHODS)
|
||||||
def test_versioned_named_routes_get(app, method):
|
def test_versioned_named_routes_get(method):
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
bp = Blueprint("test_bp", url_prefix="/bp")
|
bp = Blueprint("test_bp", url_prefix="/bp")
|
||||||
|
|
||||||
method = method.lower()
|
method = method.lower()
|
||||||
|
@ -32,7 +35,6 @@ def test_versioned_named_routes_get(app, method):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(func)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
func = getattr(bp, method)
|
func = getattr(bp, method)
|
||||||
|
@ -43,15 +45,28 @@ def test_versioned_named_routes_get(app, method):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(func)
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
|
|
||||||
assert app.router.routes_all[f"/v1/{method}"].name == route_name
|
assert (
|
||||||
|
app.router.routes_all[
|
||||||
|
(
|
||||||
|
"v1",
|
||||||
|
method,
|
||||||
|
)
|
||||||
|
].name
|
||||||
|
== f"app.{route_name}"
|
||||||
|
)
|
||||||
|
|
||||||
route = app.router.routes_all[f"/v1/bp/{method}"]
|
route = app.router.routes_all[
|
||||||
assert route.name == f"test_bp.{route_name2}"
|
(
|
||||||
|
"v1",
|
||||||
|
"bp",
|
||||||
|
method,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
assert route.name == f"app.test_bp.{route_name2}"
|
||||||
|
|
||||||
assert app.url_for(route_name) == f"/v1/{method}"
|
assert app.url_for(route_name) == f"/v1/{method}"
|
||||||
url = app.url_for(f"test_bp.{route_name2}")
|
url = app.url_for(f"test_bp.{route_name2}")
|
||||||
|
@ -60,16 +75,19 @@ def test_versioned_named_routes_get(app, method):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_shorthand_default_routes_get(app):
|
def test_shorthand_default_routes_get():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.get("/get")
|
@app.get("/get")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
assert app.router.routes_all["/get"].name == "handler"
|
assert app.router.routes_all[("get",)].name == "app.handler"
|
||||||
assert app.url_for("handler") == "/get"
|
assert app.url_for("handler") == "/get"
|
||||||
|
|
||||||
|
|
||||||
def test_shorthand_named_routes_get(app):
|
def test_shorthand_named_routes_get():
|
||||||
|
app = Sanic("app")
|
||||||
bp = Blueprint("test_bp", url_prefix="/bp")
|
bp = Blueprint("test_bp", url_prefix="/bp")
|
||||||
|
|
||||||
@app.get("/get", name="route_get")
|
@app.get("/get", name="route_get")
|
||||||
|
@ -82,84 +100,106 @@ def test_shorthand_named_routes_get(app):
|
||||||
|
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
|
|
||||||
assert app.router.routes_all["/get"].name == "route_get"
|
assert app.router.routes_all[("get",)].name == "app.route_get"
|
||||||
assert app.url_for("route_get") == "/get"
|
assert app.url_for("route_get") == "/get"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
assert app.router.routes_all["/bp/get"].name == "test_bp.route_bp"
|
assert (
|
||||||
|
app.router.routes_all[
|
||||||
|
(
|
||||||
|
"bp",
|
||||||
|
"get",
|
||||||
|
)
|
||||||
|
].name
|
||||||
|
== "app.test_bp.route_bp"
|
||||||
|
)
|
||||||
assert app.url_for("test_bp.route_bp") == "/bp/get"
|
assert app.url_for("test_bp.route_bp") == "/bp/get"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("test_bp.handler2")
|
app.url_for("test_bp.handler2")
|
||||||
|
|
||||||
|
|
||||||
def test_shorthand_named_routes_post(app):
|
def test_shorthand_named_routes_post():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.post("/post", name="route_name")
|
@app.post("/post", name="route_name")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
assert app.router.routes_all["/post"].name == "route_name"
|
assert app.router.routes_all[("post",)].name == "app.route_name"
|
||||||
assert app.url_for("route_name") == "/post"
|
assert app.url_for("route_name") == "/post"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_shorthand_named_routes_put(app):
|
def test_shorthand_named_routes_put():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.put("/put", name="route_put")
|
@app.put("/put", name="route_put")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
assert app.router.routes_all["/put"].name == "route_put"
|
assert app.router.routes_all[("put",)].name == "app.route_put"
|
||||||
assert app.url_for("route_put") == "/put"
|
assert app.url_for("route_put") == "/put"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_shorthand_named_routes_delete(app):
|
def test_shorthand_named_routes_delete():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.delete("/delete", name="route_delete")
|
@app.delete("/delete", name="route_delete")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
assert app.router.routes_all["/delete"].name == "route_delete"
|
assert app.router.routes_all[("delete",)].name == "app.route_delete"
|
||||||
assert app.url_for("route_delete") == "/delete"
|
assert app.url_for("route_delete") == "/delete"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_shorthand_named_routes_patch(app):
|
def test_shorthand_named_routes_patch():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.patch("/patch", name="route_patch")
|
@app.patch("/patch", name="route_patch")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
assert app.router.routes_all["/patch"].name == "route_patch"
|
assert app.router.routes_all[("patch",)].name == "app.route_patch"
|
||||||
assert app.url_for("route_patch") == "/patch"
|
assert app.url_for("route_patch") == "/patch"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_shorthand_named_routes_head(app):
|
def test_shorthand_named_routes_head():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.head("/head", name="route_head")
|
@app.head("/head", name="route_head")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
assert app.router.routes_all["/head"].name == "route_head"
|
assert app.router.routes_all[("head",)].name == "app.route_head"
|
||||||
assert app.url_for("route_head") == "/head"
|
assert app.url_for("route_head") == "/head"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_shorthand_named_routes_options(app):
|
def test_shorthand_named_routes_options():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.options("/options", name="route_options")
|
@app.options("/options", name="route_options")
|
||||||
def handler(request):
|
def handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
assert app.router.routes_all["/options"].name == "route_options"
|
assert app.router.routes_all[("options",)].name == "app.route_options"
|
||||||
assert app.url_for("route_options") == "/options"
|
assert app.url_for("route_options") == "/options"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_named_static_routes(app):
|
def test_named_static_routes():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.route("/test", name="route_test")
|
@app.route("/test", name="route_test")
|
||||||
async def handler1(request):
|
async def handler1(request):
|
||||||
return text("OK1")
|
return text("OK1")
|
||||||
|
@ -168,20 +208,21 @@ def test_named_static_routes(app):
|
||||||
async def handler2(request):
|
async def handler2(request):
|
||||||
return text("OK2")
|
return text("OK2")
|
||||||
|
|
||||||
assert app.router.routes_all["/test"].name == "route_test"
|
assert app.router.routes_all[("test",)].name == "app.route_test"
|
||||||
assert app.router.routes_static["/test"].name == "route_test"
|
assert app.router.routes_static[("test",)].name == "app.route_test"
|
||||||
assert app.url_for("route_test") == "/test"
|
assert app.url_for("route_test") == "/test"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler1")
|
app.url_for("handler1")
|
||||||
|
|
||||||
assert app.router.routes_all["/pizazz"].name == "route_pizazz"
|
assert app.router.routes_all[("pizazz",)].name == "app.route_pizazz"
|
||||||
assert app.router.routes_static["/pizazz"].name == "route_pizazz"
|
assert app.router.routes_static[("pizazz",)].name == "app.route_pizazz"
|
||||||
assert app.url_for("route_pizazz") == "/pizazz"
|
assert app.url_for("route_pizazz") == "/pizazz"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler2")
|
app.url_for("handler2")
|
||||||
|
|
||||||
|
|
||||||
def test_named_dynamic_route(app):
|
def test_named_dynamic_route():
|
||||||
|
app = Sanic("app")
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
@app.route("/folder/<name>", name="route_dynamic")
|
@app.route("/folder/<name>", name="route_dynamic")
|
||||||
|
@ -189,52 +230,83 @@ def test_named_dynamic_route(app):
|
||||||
results.append(name)
|
results.append(name)
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
assert app.router.routes_all["/folder/<name>"].name == "route_dynamic"
|
assert (
|
||||||
|
app.router.routes_all[
|
||||||
|
(
|
||||||
|
"folder",
|
||||||
|
"<name>",
|
||||||
|
)
|
||||||
|
].name
|
||||||
|
== "app.route_dynamic"
|
||||||
|
)
|
||||||
assert app.url_for("route_dynamic", name="test") == "/folder/test"
|
assert app.url_for("route_dynamic", name="test") == "/folder/test"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_named_route_regex(app):
|
def test_dynamic_named_route_regex():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.route("/folder/<folder_id:[A-Za-z0-9]{0,4}>", name="route_re")
|
@app.route("/folder/<folder_id:[A-Za-z0-9]{0,4}>", name="route_re")
|
||||||
async def handler(request, folder_id):
|
async def handler(request, folder_id):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
route = app.router.routes_all["/folder/<folder_id:[A-Za-z0-9]{0,4}>"]
|
route = app.router.routes_all[
|
||||||
assert route.name == "route_re"
|
(
|
||||||
|
"folder",
|
||||||
|
"<folder_id:[A-Za-z0-9]{0,4}>",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
assert route.name == "app.route_re"
|
||||||
assert app.url_for("route_re", folder_id="test") == "/folder/test"
|
assert app.url_for("route_re", folder_id="test") == "/folder/test"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_named_route_path(app):
|
def test_dynamic_named_route_path():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.route("/<path:path>/info", name="route_dynamic_path")
|
@app.route("/<path:path>/info", name="route_dynamic_path")
|
||||||
async def handler(request, path):
|
async def handler(request, path):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
route = app.router.routes_all["/<path:path>/info"]
|
route = app.router.routes_all[
|
||||||
assert route.name == "route_dynamic_path"
|
(
|
||||||
|
"<path:path>",
|
||||||
|
"info",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
assert route.name == "app.route_dynamic_path"
|
||||||
assert app.url_for("route_dynamic_path", path="path/1") == "/path/1/info"
|
assert app.url_for("route_dynamic_path", path="path/1") == "/path/1/info"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_named_route_unhashable(app):
|
def test_dynamic_named_route_unhashable():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.route(
|
@app.route(
|
||||||
"/folder/<unhashable:[A-Za-z0-9/]+>/end/", name="route_unhashable"
|
"/folder/<unhashable:[A-Za-z0-9/]+>/end/", name="route_unhashable"
|
||||||
)
|
)
|
||||||
async def handler(request, unhashable):
|
async def handler(request, unhashable):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
route = app.router.routes_all["/folder/<unhashable:[A-Za-z0-9/]+>/end/"]
|
route = app.router.routes_all[
|
||||||
assert route.name == "route_unhashable"
|
(
|
||||||
|
"folder",
|
||||||
|
"<unhashable:[A-Za-z0-9/]+>",
|
||||||
|
"end",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
assert route.name == "app.route_unhashable"
|
||||||
url = app.url_for("route_unhashable", unhashable="test/asdf")
|
url = app.url_for("route_unhashable", unhashable="test/asdf")
|
||||||
assert url == "/folder/test/asdf/end"
|
assert url == "/folder/test/asdf/end"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_websocket_named_route(app):
|
def test_websocket_named_route():
|
||||||
|
app = Sanic("app")
|
||||||
ev = asyncio.Event()
|
ev = asyncio.Event()
|
||||||
|
|
||||||
@app.websocket("/ws", name="route_ws")
|
@app.websocket("/ws", name="route_ws")
|
||||||
|
@ -242,26 +314,29 @@ def test_websocket_named_route(app):
|
||||||
assert ws.subprotocol is None
|
assert ws.subprotocol is None
|
||||||
ev.set()
|
ev.set()
|
||||||
|
|
||||||
assert app.router.routes_all["/ws"].name == "route_ws"
|
assert app.router.routes_all[("ws",)].name == "app.route_ws"
|
||||||
assert app.url_for("route_ws") == "/ws"
|
assert app.url_for("route_ws") == "/ws"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_websocket_named_route_with_subprotocols(app):
|
def test_websocket_named_route_with_subprotocols():
|
||||||
|
app = Sanic("app")
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
@app.websocket("/ws", subprotocols=["foo", "bar"], name="route_ws")
|
@app.websocket("/ws", subprotocols=["foo", "bar"], name="route_ws")
|
||||||
async def handler(request, ws):
|
async def handler(request, ws):
|
||||||
results.append(ws.subprotocol)
|
results.append(ws.subprotocol)
|
||||||
|
|
||||||
assert app.router.routes_all["/ws"].name == "route_ws"
|
assert app.router.routes_all[("ws",)].name == "app.route_ws"
|
||||||
assert app.url_for("route_ws") == "/ws"
|
assert app.url_for("route_ws") == "/ws"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_static_add_named_route(app):
|
def test_static_add_named_route():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
async def handler1(request):
|
async def handler1(request):
|
||||||
return text("OK1")
|
return text("OK1")
|
||||||
|
|
||||||
|
@ -271,20 +346,21 @@ def test_static_add_named_route(app):
|
||||||
app.add_route(handler1, "/test", name="route_test")
|
app.add_route(handler1, "/test", name="route_test")
|
||||||
app.add_route(handler2, "/test2", name="route_test2")
|
app.add_route(handler2, "/test2", name="route_test2")
|
||||||
|
|
||||||
assert app.router.routes_all["/test"].name == "route_test"
|
assert app.router.routes_all[("test",)].name == "app.route_test"
|
||||||
assert app.router.routes_static["/test"].name == "route_test"
|
assert app.router.routes_static[("test",)].name == "app.route_test"
|
||||||
assert app.url_for("route_test") == "/test"
|
assert app.url_for("route_test") == "/test"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler1")
|
app.url_for("handler1")
|
||||||
|
|
||||||
assert app.router.routes_all["/test2"].name == "route_test2"
|
assert app.router.routes_all[("test2",)].name == "app.route_test2"
|
||||||
assert app.router.routes_static["/test2"].name == "route_test2"
|
assert app.router.routes_static[("test2",)].name == "app.route_test2"
|
||||||
assert app.url_for("route_test2") == "/test2"
|
assert app.url_for("route_test2") == "/test2"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler2")
|
app.url_for("handler2")
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_add_named_route(app):
|
def test_dynamic_add_named_route():
|
||||||
|
app = Sanic("app")
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
async def handler(request, name):
|
async def handler(request, name):
|
||||||
|
@ -292,13 +368,17 @@ def test_dynamic_add_named_route(app):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
app.add_route(handler, "/folder/<name>", name="route_dynamic")
|
app.add_route(handler, "/folder/<name>", name="route_dynamic")
|
||||||
assert app.router.routes_all["/folder/<name>"].name == "route_dynamic"
|
assert (
|
||||||
|
app.router.routes_all[("folder", "<name>")].name == "app.route_dynamic"
|
||||||
|
)
|
||||||
assert app.url_for("route_dynamic", name="test") == "/folder/test"
|
assert app.url_for("route_dynamic", name="test") == "/folder/test"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_dynamic_add_named_route_unhashable(app):
|
def test_dynamic_add_named_route_unhashable():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
async def handler(request, unhashable):
|
async def handler(request, unhashable):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
|
@ -307,15 +387,23 @@ def test_dynamic_add_named_route_unhashable(app):
|
||||||
"/folder/<unhashable:[A-Za-z0-9/]+>/end/",
|
"/folder/<unhashable:[A-Za-z0-9/]+>/end/",
|
||||||
name="route_unhashable",
|
name="route_unhashable",
|
||||||
)
|
)
|
||||||
route = app.router.routes_all["/folder/<unhashable:[A-Za-z0-9/]+>/end/"]
|
route = app.router.routes_all[
|
||||||
assert route.name == "route_unhashable"
|
(
|
||||||
|
"folder",
|
||||||
|
"<unhashable:[A-Za-z0-9/]+>",
|
||||||
|
"end",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
assert route.name == "app.route_unhashable"
|
||||||
url = app.url_for("route_unhashable", unhashable="folder1")
|
url = app.url_for("route_unhashable", unhashable="folder1")
|
||||||
assert url == "/folder/folder1/end"
|
assert url == "/folder/folder1/end"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler")
|
app.url_for("handler")
|
||||||
|
|
||||||
|
|
||||||
def test_overload_routes(app):
|
def test_overload_routes():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.route("/overload", methods=["GET"], name="route_first")
|
@app.route("/overload", methods=["GET"], name="route_first")
|
||||||
async def handler1(request):
|
async def handler1(request):
|
||||||
return text("OK1")
|
return text("OK1")
|
||||||
|
@ -342,7 +430,7 @@ def test_overload_routes(app):
|
||||||
request, response = app.test_client.put(app.url_for("route_second"))
|
request, response = app.test_client.put(app.url_for("route_second"))
|
||||||
assert response.text == "OK2"
|
assert response.text == "OK2"
|
||||||
|
|
||||||
assert app.router.routes_all["/overload"].name == "route_first"
|
assert app.router.routes_all[("overload",)].name == "app.route_first"
|
||||||
with pytest.raises(URLBuildError):
|
with pytest.raises(URLBuildError):
|
||||||
app.url_for("handler1")
|
app.url_for("handler1")
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ def test_payload_too_large_from_error_handler(app):
|
||||||
def handler_exception(request, exception):
|
def handler_exception(request, exception):
|
||||||
return text("Payload Too Large from error_handler.", 413)
|
return text("Payload Too Large from error_handler.", 413)
|
||||||
|
|
||||||
response = app.test_client.get("/1", gather_request=False)
|
_, response = app.test_client.get("/1", gather_request=False)
|
||||||
assert response.status == 413
|
assert response.status == 413
|
||||||
assert response.text == "Payload Too Large from error_handler."
|
assert response.text == "Payload Too Large from error_handler."
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ def test_payload_too_large_at_data_received_default(app):
|
||||||
async def handler2(request):
|
async def handler2(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
response = app.test_client.get("/1", gather_request=False)
|
_, response = app.test_client.get("/1", gather_request=False)
|
||||||
assert response.status == 413
|
assert response.status == 413
|
||||||
assert "Request header" in response.text
|
assert "Request header" in response.text
|
||||||
|
|
||||||
|
@ -38,6 +38,6 @@ def test_payload_too_large_at_on_header_default(app):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
data = "a" * 1000
|
data = "a" * 1000
|
||||||
response = app.test_client.post("/1", gather_request=False, data=data)
|
_, response = app.test_client.post("/1", gather_request=False, data=data)
|
||||||
assert response.status == 413
|
assert response.status == 413
|
||||||
assert "Request body" in response.text
|
assert "Request body" in response.text
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote, unquote
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -109,7 +109,14 @@ def test_redirect_with_header_injection(redirect_app):
|
||||||
assert not response.text.startswith("test-body")
|
assert not response.text.startswith("test-body")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("test_str", ["sanic-test", "sanictest", "sanic test"])
|
@pytest.mark.parametrize(
|
||||||
|
"test_str",
|
||||||
|
[
|
||||||
|
"sanic-test",
|
||||||
|
"sanictest",
|
||||||
|
"sanic test",
|
||||||
|
],
|
||||||
|
)
|
||||||
def test_redirect_with_params(app, test_str):
|
def test_redirect_with_params(app, test_str):
|
||||||
use_in_uri = quote(test_str)
|
use_in_uri = quote(test_str)
|
||||||
|
|
||||||
|
@ -117,7 +124,7 @@ def test_redirect_with_params(app, test_str):
|
||||||
async def init_handler(request, test):
|
async def init_handler(request, test):
|
||||||
return redirect(f"/api/v2/test/{use_in_uri}/")
|
return redirect(f"/api/v2/test/{use_in_uri}/")
|
||||||
|
|
||||||
@app.route("/api/v2/test/<test>/")
|
@app.route("/api/v2/test/<test>/", unquote=True)
|
||||||
async def target_handler(request, test):
|
async def target_handler(request, test):
|
||||||
assert test == test_str
|
assert test == test_str
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
@ -125,4 +132,4 @@ def test_redirect_with_params(app, test_str):
|
||||||
_, response = app.test_client.get(f"/api/v1/test/{use_in_uri}/")
|
_, response = app.test_client.get(f"/api/v1/test/{use_in_uri}/")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
assert response.content == b"OK"
|
assert response.body == b"OK"
|
||||||
|
|
|
@ -42,6 +42,8 @@ def write_app(filename, **runargs):
|
||||||
|
|
||||||
app = Sanic(__name__)
|
app = Sanic(__name__)
|
||||||
|
|
||||||
|
app.route("/")(lambda x: x)
|
||||||
|
|
||||||
@app.listener("after_server_start")
|
@app.listener("after_server_start")
|
||||||
def complete(*args):
|
def complete(*args):
|
||||||
print("complete", os.getpid(), {text!r})
|
print("complete", os.getpid(), {text!r})
|
||||||
|
|
|
@ -10,7 +10,6 @@ import pytest
|
||||||
|
|
||||||
from sanic_testing.testing import (
|
from sanic_testing.testing import (
|
||||||
ASGI_BASE_URL,
|
ASGI_BASE_URL,
|
||||||
ASGI_HOST,
|
|
||||||
ASGI_PORT,
|
ASGI_PORT,
|
||||||
HOST,
|
HOST,
|
||||||
PORT,
|
PORT,
|
||||||
|
@ -19,7 +18,7 @@ from sanic_testing.testing import (
|
||||||
|
|
||||||
from sanic import Blueprint, Sanic
|
from sanic import Blueprint, Sanic
|
||||||
from sanic.exceptions import ServerError
|
from sanic.exceptions import ServerError
|
||||||
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE, Request, RequestParameters
|
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE, RequestParameters
|
||||||
from sanic.response import html, json, text
|
from sanic.response import html, json, text
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,7 +34,7 @@ def test_sync(app):
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
|
|
||||||
assert response.text == "Hello"
|
assert response.body == b"Hello"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -46,7 +45,7 @@ async def test_sync_asgi(app):
|
||||||
|
|
||||||
request, response = await app.asgi_client.get("/")
|
request, response = await app.asgi_client.get("/")
|
||||||
|
|
||||||
assert response.text == "Hello"
|
assert response.body == b"Hello"
|
||||||
|
|
||||||
|
|
||||||
def test_ip(app):
|
def test_ip(app):
|
||||||
|
@ -56,7 +55,7 @@ def test_ip(app):
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
|
|
||||||
assert response.text == "127.0.0.1"
|
assert response.body == b"127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -67,10 +66,12 @@ async def test_url_asgi(app):
|
||||||
|
|
||||||
request, response = await app.asgi_client.get("/")
|
request, response = await app.asgi_client.get("/")
|
||||||
|
|
||||||
if response.text.endswith("/") and not ASGI_BASE_URL.endswith("/"):
|
if response.body.decode().endswith("/") and not ASGI_BASE_URL.endswith(
|
||||||
response.text[:-1] == ASGI_BASE_URL
|
"/"
|
||||||
|
):
|
||||||
|
response.body[:-1] == ASGI_BASE_URL.encode()
|
||||||
else:
|
else:
|
||||||
assert response.text == ASGI_BASE_URL
|
assert response.body == ASGI_BASE_URL.encode()
|
||||||
|
|
||||||
|
|
||||||
def test_text(app):
|
def test_text(app):
|
||||||
|
@ -80,7 +81,7 @@ def test_text(app):
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
|
|
||||||
assert response.text == "Hello"
|
assert response.body == b"Hello"
|
||||||
|
|
||||||
|
|
||||||
def test_html(app):
|
def test_html(app):
|
||||||
|
@ -109,13 +110,13 @@ def test_html(app):
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
assert response.content_type == "text/html; charset=utf-8"
|
assert response.content_type == "text/html; charset=utf-8"
|
||||||
assert response.text == "<h1>Hello</h1>"
|
assert response.body == b"<h1>Hello</h1>"
|
||||||
|
|
||||||
request, response = app.test_client.get("/foo")
|
request, response = app.test_client.get("/foo")
|
||||||
assert response.text == "<h1>Foo</h1>"
|
assert response.body == b"<h1>Foo</h1>"
|
||||||
|
|
||||||
request, response = app.test_client.get("/bar")
|
request, response = app.test_client.get("/bar")
|
||||||
assert response.text == "<h1>Bar object repr</h1>"
|
assert response.body == b"<h1>Bar object repr</h1>"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -126,7 +127,7 @@ async def test_text_asgi(app):
|
||||||
|
|
||||||
request, response = await app.asgi_client.get("/")
|
request, response = await app.asgi_client.get("/")
|
||||||
|
|
||||||
assert response.text == "Hello"
|
assert response.body == b"Hello"
|
||||||
|
|
||||||
|
|
||||||
def test_headers(app):
|
def test_headers(app):
|
||||||
|
@ -186,7 +187,7 @@ def test_invalid_response(app):
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
assert response.status == 500
|
assert response.status == 500
|
||||||
assert response.text == "Internal Server Error."
|
assert response.body == b"Internal Server Error."
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -201,7 +202,7 @@ async def test_invalid_response_asgi(app):
|
||||||
|
|
||||||
request, response = await app.asgi_client.get("/")
|
request, response = await app.asgi_client.get("/")
|
||||||
assert response.status == 500
|
assert response.status == 500
|
||||||
assert response.text == "Internal Server Error."
|
assert response.body == b"Internal Server Error."
|
||||||
|
|
||||||
|
|
||||||
def test_json(app):
|
def test_json(app):
|
||||||
|
@ -224,7 +225,7 @@ async def test_json_asgi(app):
|
||||||
|
|
||||||
request, response = await app.asgi_client.get("/")
|
request, response = await app.asgi_client.get("/")
|
||||||
|
|
||||||
results = json_loads(response.text)
|
results = json_loads(response.body)
|
||||||
|
|
||||||
assert results.get("test") is True
|
assert results.get("test") is True
|
||||||
|
|
||||||
|
@ -237,7 +238,7 @@ def test_empty_json(app):
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == "null"
|
assert response.body == b"null"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -249,7 +250,7 @@ async def test_empty_json_asgi(app):
|
||||||
|
|
||||||
request, response = await app.asgi_client.get("/")
|
request, response = await app.asgi_client.get("/")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == "null"
|
assert response.body == b"null"
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_json(app):
|
def test_invalid_json(app):
|
||||||
|
@ -423,12 +424,12 @@ def test_content_type(app):
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
assert request.content_type == DEFAULT_HTTP_CONTENT_TYPE
|
assert request.content_type == DEFAULT_HTTP_CONTENT_TYPE
|
||||||
assert response.text == DEFAULT_HTTP_CONTENT_TYPE
|
assert response.body.decode() == DEFAULT_HTTP_CONTENT_TYPE
|
||||||
|
|
||||||
headers = {"content-type": "application/json"}
|
headers = {"content-type": "application/json"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.content_type == "application/json"
|
assert request.content_type == "application/json"
|
||||||
assert response.text == "application/json"
|
assert response.body == b"application/json"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -439,12 +440,12 @@ async def test_content_type_asgi(app):
|
||||||
|
|
||||||
request, response = await app.asgi_client.get("/")
|
request, response = await app.asgi_client.get("/")
|
||||||
assert request.content_type == DEFAULT_HTTP_CONTENT_TYPE
|
assert request.content_type == DEFAULT_HTTP_CONTENT_TYPE
|
||||||
assert response.text == DEFAULT_HTTP_CONTENT_TYPE
|
assert response.body.decode() == DEFAULT_HTTP_CONTENT_TYPE
|
||||||
|
|
||||||
headers = {"content-type": "application/json"}
|
headers = {"content-type": "application/json"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.content_type == "application/json"
|
assert request.content_type == "application/json"
|
||||||
assert response.text == "application/json"
|
assert response.body == b"application/json"
|
||||||
|
|
||||||
|
|
||||||
def test_standard_forwarded(app):
|
def test_standard_forwarded(app):
|
||||||
|
@ -581,14 +582,15 @@ async def test_standard_forwarded_asgi(app):
|
||||||
"X-Scheme": "ws",
|
"X-Scheme": "ws",
|
||||||
}
|
}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {"for": "127.0.0.2", "proto": "ws"}
|
|
||||||
|
assert response.json == {"for": "127.0.0.2", "proto": "ws"}
|
||||||
assert request.remote_addr == "127.0.0.2"
|
assert request.remote_addr == "127.0.0.2"
|
||||||
assert request.scheme == "ws"
|
assert request.scheme == "ws"
|
||||||
assert request.server_port == ASGI_PORT
|
assert request.server_port == ASGI_PORT
|
||||||
|
|
||||||
app.config.FORWARDED_SECRET = "mySecret"
|
app.config.FORWARDED_SECRET = "mySecret"
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {
|
assert response.json == {
|
||||||
"for": "[::2]",
|
"for": "[::2]",
|
||||||
"proto": "https",
|
"proto": "https",
|
||||||
"host": "me.tld",
|
"host": "me.tld",
|
||||||
|
@ -603,13 +605,13 @@ async def test_standard_forwarded_asgi(app):
|
||||||
# Empty Forwarded header -> use X-headers
|
# Empty Forwarded header -> use X-headers
|
||||||
headers["Forwarded"] = ""
|
headers["Forwarded"] = ""
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {"for": "127.0.0.2", "proto": "ws"}
|
assert response.json == {"for": "127.0.0.2", "proto": "ws"}
|
||||||
|
|
||||||
# Header present but not matching anything
|
# Header present but not matching anything
|
||||||
request, response = await app.asgi_client.get(
|
request, response = await app.asgi_client.get(
|
||||||
"/", headers={"Forwarded": "."}
|
"/", headers={"Forwarded": "."}
|
||||||
)
|
)
|
||||||
assert response.json() == {}
|
assert response.json == {}
|
||||||
|
|
||||||
# Forwarded header present but no matching secret -> use X-headers
|
# Forwarded header present but no matching secret -> use X-headers
|
||||||
headers = {
|
headers = {
|
||||||
|
@ -617,13 +619,13 @@ async def test_standard_forwarded_asgi(app):
|
||||||
"X-Real-IP": "127.0.0.2",
|
"X-Real-IP": "127.0.0.2",
|
||||||
}
|
}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {"for": "127.0.0.2"}
|
assert response.json == {"for": "127.0.0.2"}
|
||||||
assert request.remote_addr == "127.0.0.2"
|
assert request.remote_addr == "127.0.0.2"
|
||||||
|
|
||||||
# Different formatting and hitting both ends of the header
|
# Different formatting and hitting both ends of the header
|
||||||
headers = {"Forwarded": 'Secret="mySecret";For=127.0.0.4;Port=1234'}
|
headers = {"Forwarded": 'Secret="mySecret";For=127.0.0.4;Port=1234'}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {
|
assert response.json == {
|
||||||
"for": "127.0.0.4",
|
"for": "127.0.0.4",
|
||||||
"port": 1234,
|
"port": 1234,
|
||||||
"secret": "mySecret",
|
"secret": "mySecret",
|
||||||
|
@ -632,7 +634,7 @@ async def test_standard_forwarded_asgi(app):
|
||||||
# Test escapes (modify this if you see anyone implementing quoted-pairs)
|
# Test escapes (modify this if you see anyone implementing quoted-pairs)
|
||||||
headers = {"Forwarded": 'for=test;quoted="\\,x=x;y=\\";secret=mySecret'}
|
headers = {"Forwarded": 'for=test;quoted="\\,x=x;y=\\";secret=mySecret'}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {
|
assert response.json == {
|
||||||
"for": "test",
|
"for": "test",
|
||||||
"quoted": "\\,x=x;y=\\",
|
"quoted": "\\,x=x;y=\\",
|
||||||
"secret": "mySecret",
|
"secret": "mySecret",
|
||||||
|
@ -641,17 +643,17 @@ async def test_standard_forwarded_asgi(app):
|
||||||
# Secret insulated by malformed field #1
|
# Secret insulated by malformed field #1
|
||||||
headers = {"Forwarded": "for=test;secret=mySecret;b0rked;proto=wss;"}
|
headers = {"Forwarded": "for=test;secret=mySecret;b0rked;proto=wss;"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {"for": "test", "secret": "mySecret"}
|
assert response.json == {"for": "test", "secret": "mySecret"}
|
||||||
|
|
||||||
# Secret insulated by malformed field #2
|
# Secret insulated by malformed field #2
|
||||||
headers = {"Forwarded": "for=test;b0rked;secret=mySecret;proto=wss"}
|
headers = {"Forwarded": "for=test;b0rked;secret=mySecret;proto=wss"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {"proto": "wss", "secret": "mySecret"}
|
assert response.json == {"proto": "wss", "secret": "mySecret"}
|
||||||
|
|
||||||
# Unexpected termination should not lose existing acceptable values
|
# Unexpected termination should not lose existing acceptable values
|
||||||
headers = {"Forwarded": "b0rked;secret=mySecret;proto=wss"}
|
headers = {"Forwarded": "b0rked;secret=mySecret;proto=wss"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {"proto": "wss", "secret": "mySecret"}
|
assert response.json == {"proto": "wss", "secret": "mySecret"}
|
||||||
|
|
||||||
# Field normalization
|
# Field normalization
|
||||||
headers = {
|
headers = {
|
||||||
|
@ -659,7 +661,7 @@ async def test_standard_forwarded_asgi(app):
|
||||||
'PATH="/With%20Spaces%22Quoted%22/sanicApp?key=val";SECRET=mySecret'
|
'PATH="/With%20Spaces%22Quoted%22/sanicApp?key=val";SECRET=mySecret'
|
||||||
}
|
}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {
|
assert response.json == {
|
||||||
"proto": "wss",
|
"proto": "wss",
|
||||||
"by": "[cafe::8000]",
|
"by": "[cafe::8000]",
|
||||||
"host": "a:2",
|
"host": "a:2",
|
||||||
|
@ -671,7 +673,10 @@ async def test_standard_forwarded_asgi(app):
|
||||||
app.config.FORWARDED_SECRET = "_proxySecret"
|
app.config.FORWARDED_SECRET = "_proxySecret"
|
||||||
headers = {"Forwarded": "for=1.2.3.4; by=_proxySecret"}
|
headers = {"Forwarded": "for=1.2.3.4; by=_proxySecret"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert response.json() == {"for": "1.2.3.4", "by": "_proxySecret"}
|
assert response.json == {
|
||||||
|
"for": "1.2.3.4",
|
||||||
|
"by": "_proxySecret",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test_remote_addr_with_two_proxies(app):
|
def test_remote_addr_with_two_proxies(app):
|
||||||
|
@ -685,33 +690,33 @@ def test_remote_addr_with_two_proxies(app):
|
||||||
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.2"
|
assert request.remote_addr == "127.0.0.2"
|
||||||
assert response.text == "127.0.0.2"
|
assert response.body == b"127.0.0.2"
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.1"
|
assert request.remote_addr == "127.0.0.1"
|
||||||
assert response.text == "127.0.0.1"
|
assert response.body == b"127.0.0.1"
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.0.1, , ,,127.0.1.2"}
|
headers = {"X-Forwarded-For": "127.0.0.1, , ,,127.0.1.2"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.1"
|
assert request.remote_addr == "127.0.0.1"
|
||||||
assert response.text == "127.0.0.1"
|
assert response.body == b"127.0.0.1"
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"X-Forwarded-For": ", 127.0.2.2, , ,127.0.0.1, , ,,127.0.1.2"
|
"X-Forwarded-For": ", 127.0.2.2, , ,127.0.0.1, , ,,127.0.1.2"
|
||||||
}
|
}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.1"
|
assert request.remote_addr == "127.0.0.1"
|
||||||
assert response.text == "127.0.0.1"
|
assert response.body == b"127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -726,33 +731,33 @@ async def test_remote_addr_with_two_proxies_asgi(app):
|
||||||
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.2"
|
assert request.remote_addr == "127.0.0.2"
|
||||||
assert response.text == "127.0.0.2"
|
assert response.body == b"127.0.0.2"
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.1"
|
assert request.remote_addr == "127.0.0.1"
|
||||||
assert response.text == "127.0.0.1"
|
assert response.body == b"127.0.0.1"
|
||||||
|
|
||||||
request, response = await app.asgi_client.get("/")
|
request, response = await app.asgi_client.get("/")
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.0.1, , ,,127.0.1.2"}
|
headers = {"X-Forwarded-For": "127.0.0.1, , ,,127.0.1.2"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.1"
|
assert request.remote_addr == "127.0.0.1"
|
||||||
assert response.text == "127.0.0.1"
|
assert response.body == b"127.0.0.1"
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
"X-Forwarded-For": ", 127.0.2.2, , ,127.0.0.1, , ,,127.0.1.2"
|
"X-Forwarded-For": ", 127.0.2.2, , ,127.0.0.1, , ,,127.0.1.2"
|
||||||
}
|
}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.1"
|
assert request.remote_addr == "127.0.0.1"
|
||||||
assert response.text == "127.0.0.1"
|
assert response.body == b"127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
def test_remote_addr_without_proxy(app):
|
def test_remote_addr_without_proxy(app):
|
||||||
|
@ -765,17 +770,17 @@ def test_remote_addr_without_proxy(app):
|
||||||
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -789,17 +794,17 @@ async def test_remote_addr_without_proxy_asgi(app):
|
||||||
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Real-IP": "127.0.0.2", "X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
headers = {"X-Forwarded-For": "127.0.0.1, 127.0.1.2"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
|
|
||||||
def test_remote_addr_custom_headers(app):
|
def test_remote_addr_custom_headers(app):
|
||||||
|
@ -814,17 +819,17 @@ def test_remote_addr_custom_headers(app):
|
||||||
headers = {"X-Real-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
headers = {"X-Real-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.1.1"
|
assert request.remote_addr == "127.0.1.1"
|
||||||
assert response.text == "127.0.1.1"
|
assert response.body == b"127.0.1.1"
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"Client-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
headers = {"Client-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.2"
|
assert request.remote_addr == "127.0.0.2"
|
||||||
assert response.text == "127.0.0.2"
|
assert response.body == b"127.0.0.2"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -840,17 +845,17 @@ async def test_remote_addr_custom_headers_asgi(app):
|
||||||
headers = {"X-Real-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
headers = {"X-Real-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.1.1"
|
assert request.remote_addr == "127.0.1.1"
|
||||||
assert response.text == "127.0.1.1"
|
assert response.body == b"127.0.1.1"
|
||||||
|
|
||||||
headers = {"X-Forwarded-For": "127.0.1.1"}
|
headers = {"X-Forwarded-For": "127.0.1.1"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == ""
|
assert request.remote_addr == ""
|
||||||
assert response.text == ""
|
assert response.body == b""
|
||||||
|
|
||||||
headers = {"Client-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
headers = {"Client-IP": "127.0.0.2", "Forwarded": "127.0.1.1"}
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
assert request.remote_addr == "127.0.0.2"
|
assert request.remote_addr == "127.0.0.2"
|
||||||
assert response.text == "127.0.0.2"
|
assert response.body == b"127.0.0.2"
|
||||||
|
|
||||||
|
|
||||||
def test_forwarded_scheme(app):
|
def test_forwarded_scheme(app):
|
||||||
|
@ -894,7 +899,7 @@ async def test_match_info_asgi(app):
|
||||||
request, response = await app.asgi_client.get("/api/v1/user/sanic_user/")
|
request, response = await app.asgi_client.get("/api/v1/user/sanic_user/")
|
||||||
|
|
||||||
assert request.match_info == {"user_id": "sanic_user"}
|
assert request.match_info == {"user_id": "sanic_user"}
|
||||||
assert json_loads(response.text) == {"user_id": "sanic_user"}
|
assert json_loads(response.body) == {"user_id": "sanic_user"}
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
|
@ -916,7 +921,7 @@ def test_post_json(app):
|
||||||
|
|
||||||
assert request.json.get("test") == "OK"
|
assert request.json.get("test") == "OK"
|
||||||
assert request.json.get("test") == "OK" # for request.parsed_json
|
assert request.json.get("test") == "OK" # for request.parsed_json
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -934,7 +939,7 @@ async def test_post_json_asgi(app):
|
||||||
|
|
||||||
assert request.json.get("test") == "OK"
|
assert request.json.get("test") == "OK"
|
||||||
assert request.json.get("test") == "OK" # for request.parsed_json
|
assert request.json.get("test") == "OK" # for request.parsed_json
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
|
|
||||||
def test_post_form_urlencoded(app):
|
def test_post_form_urlencoded(app):
|
||||||
|
@ -2136,7 +2141,7 @@ def test_safe_method_with_body_ignored(app):
|
||||||
|
|
||||||
assert request.body == b""
|
assert request.body == b""
|
||||||
assert request.json == None
|
assert request.json == None
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
||||||
|
|
||||||
def test_safe_method_with_body(app):
|
def test_safe_method_with_body(app):
|
||||||
|
@ -2153,4 +2158,4 @@ def test_safe_method_with_body(app):
|
||||||
|
|
||||||
assert request.body == data.encode("utf-8")
|
assert request.body == data.encode("utf-8")
|
||||||
assert request.json.get("test") == "OK"
|
assert request.json.get("test") == "OK"
|
||||||
assert response.text == "OK"
|
assert response.body == b"OK"
|
||||||
|
|
|
@ -14,6 +14,7 @@ import pytest
|
||||||
from aiofiles import os as async_os
|
from aiofiles import os as async_os
|
||||||
from sanic_testing.testing import HOST, PORT
|
from sanic_testing.testing import HOST, PORT
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
from sanic.response import (
|
from sanic.response import (
|
||||||
HTTPResponse,
|
HTTPResponse,
|
||||||
StreamingHTTPResponse,
|
StreamingHTTPResponse,
|
||||||
|
@ -51,16 +52,22 @@ async def sample_streaming_fn(response):
|
||||||
await response.write("bar")
|
await response.write("bar")
|
||||||
|
|
||||||
|
|
||||||
def test_method_not_allowed(app):
|
def test_method_not_allowed():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def test_get(request):
|
async def test_get(request):
|
||||||
return response.json({"hello": "world"})
|
return response.json({"hello": "world"})
|
||||||
|
|
||||||
request, response = app.test_client.head("/")
|
request, response = app.test_client.head("/")
|
||||||
assert response.headers["Allow"] == "GET"
|
assert set(response.headers["Allow"].split(", ")) == {
|
||||||
|
"GET",
|
||||||
|
}
|
||||||
|
|
||||||
request, response = app.test_client.post("/")
|
request, response = app.test_client.post("/")
|
||||||
assert response.headers["Allow"] == "GET"
|
assert set(response.headers["Allow"].split(", ")) == {"GET", "HEAD"}
|
||||||
|
|
||||||
|
app.router.reset()
|
||||||
|
|
||||||
@app.post("/")
|
@app.post("/")
|
||||||
async def test_post(request):
|
async def test_post(request):
|
||||||
|
@ -68,12 +75,20 @@ def test_method_not_allowed(app):
|
||||||
|
|
||||||
request, response = app.test_client.head("/")
|
request, response = app.test_client.head("/")
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
assert set(response.headers["Allow"].split(", ")) == {"GET", "POST"}
|
assert set(response.headers["Allow"].split(", ")) == {
|
||||||
|
"GET",
|
||||||
|
"POST",
|
||||||
|
"HEAD",
|
||||||
|
}
|
||||||
assert response.headers["Content-Length"] == "0"
|
assert response.headers["Content-Length"] == "0"
|
||||||
|
|
||||||
request, response = app.test_client.patch("/")
|
request, response = app.test_client.patch("/")
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
assert set(response.headers["Allow"].split(", ")) == {"GET", "POST"}
|
assert set(response.headers["Allow"].split(", ")) == {
|
||||||
|
"GET",
|
||||||
|
"POST",
|
||||||
|
"HEAD",
|
||||||
|
}
|
||||||
assert response.headers["Content-Length"] == "0"
|
assert response.headers["Content-Length"] == "0"
|
||||||
|
|
||||||
|
|
||||||
|
@ -237,7 +252,7 @@ def test_chunked_streaming_returns_correct_content(streaming_app):
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_chunked_streaming_returns_correct_content_asgi(streaming_app):
|
async def test_chunked_streaming_returns_correct_content_asgi(streaming_app):
|
||||||
request, response = await streaming_app.asgi_client.get("/")
|
request, response = await streaming_app.asgi_client.get("/")
|
||||||
assert response.text == "foo,bar"
|
assert response.body == b"foo,bar"
|
||||||
|
|
||||||
|
|
||||||
def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app):
|
def test_non_chunked_streaming_adds_correct_headers(non_chunked_streaming_app):
|
||||||
|
|
|
@ -1,18 +1,180 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from sanic_routing.exceptions import ParameterNameConflicts, RouteExists
|
||||||
from sanic_testing.testing import SanicTestClient
|
from sanic_testing.testing import SanicTestClient
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Blueprint, Sanic
|
||||||
from sanic.constants import HTTP_METHODS
|
from sanic.constants import HTTP_METHODS
|
||||||
|
from sanic.exceptions import NotFound
|
||||||
|
from sanic.request import Request
|
||||||
from sanic.response import json, text
|
from sanic.response import json, text
|
||||||
from sanic.router import ParameterNameConflicts, RouteDoesNotExist, RouteExists
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
@pytest.mark.parametrize(
|
||||||
# UTF-8
|
"path,headers,expected",
|
||||||
# ------------------------------------------------------------ #
|
(
|
||||||
|
# app base
|
||||||
|
(b"/", {}, 200),
|
||||||
|
(b"/", {"host": "maybe.com"}, 200),
|
||||||
|
(b"/host", {"host": "matching.com"}, 200),
|
||||||
|
(b"/host", {"host": "wrong.com"}, 404),
|
||||||
|
# app strict_slashes default
|
||||||
|
(b"/without", {}, 200),
|
||||||
|
(b"/without/", {}, 200),
|
||||||
|
(b"/with", {}, 200),
|
||||||
|
(b"/with/", {}, 200),
|
||||||
|
# app strict_slashes off - expressly
|
||||||
|
(b"/expwithout", {}, 200),
|
||||||
|
(b"/expwithout/", {}, 200),
|
||||||
|
(b"/expwith", {}, 200),
|
||||||
|
(b"/expwith/", {}, 200),
|
||||||
|
# app strict_slashes on
|
||||||
|
(b"/without/strict", {}, 200),
|
||||||
|
(b"/without/strict/", {}, 404),
|
||||||
|
(b"/with/strict", {}, 404),
|
||||||
|
(b"/with/strict/", {}, 200),
|
||||||
|
# bp1 base
|
||||||
|
(b"/bp1", {}, 200),
|
||||||
|
(b"/bp1", {"host": "maybe.com"}, 200),
|
||||||
|
(b"/bp1/host", {"host": "matching.com"}, 200), # BROKEN ON MASTER
|
||||||
|
(b"/bp1/host", {"host": "wrong.com"}, 404),
|
||||||
|
# bp1 strict_slashes default
|
||||||
|
(b"/bp1/without", {}, 200),
|
||||||
|
(b"/bp1/without/", {}, 200),
|
||||||
|
(b"/bp1/with", {}, 200),
|
||||||
|
(b"/bp1/with/", {}, 200),
|
||||||
|
# bp1 strict_slashes off - expressly
|
||||||
|
(b"/bp1/expwithout", {}, 200),
|
||||||
|
(b"/bp1/expwithout/", {}, 200),
|
||||||
|
(b"/bp1/expwith", {}, 200),
|
||||||
|
(b"/bp1/expwith/", {}, 200),
|
||||||
|
# bp1 strict_slashes on
|
||||||
|
(b"/bp1/without/strict", {}, 200),
|
||||||
|
(b"/bp1/without/strict/", {}, 404),
|
||||||
|
(b"/bp1/with/strict", {}, 404),
|
||||||
|
(b"/bp1/with/strict/", {}, 200),
|
||||||
|
# bp2 base
|
||||||
|
(b"/bp2/", {}, 200),
|
||||||
|
(b"/bp2/", {"host": "maybe.com"}, 200),
|
||||||
|
(b"/bp2/host", {"host": "matching.com"}, 200), # BROKEN ON MASTER
|
||||||
|
(b"/bp2/host", {"host": "wrong.com"}, 404),
|
||||||
|
# bp2 strict_slashes default
|
||||||
|
(b"/bp2/without", {}, 200),
|
||||||
|
(b"/bp2/without/", {}, 404),
|
||||||
|
(b"/bp2/with", {}, 404),
|
||||||
|
(b"/bp2/with/", {}, 200),
|
||||||
|
# # bp2 strict_slashes off - expressly
|
||||||
|
(b"/bp2/expwithout", {}, 200),
|
||||||
|
(b"/bp2/expwithout/", {}, 200),
|
||||||
|
(b"/bp2/expwith", {}, 200),
|
||||||
|
(b"/bp2/expwith/", {}, 200),
|
||||||
|
# # bp2 strict_slashes on
|
||||||
|
(b"/bp2/without/strict", {}, 200),
|
||||||
|
(b"/bp2/without/strict/", {}, 404),
|
||||||
|
(b"/bp2/with/strict", {}, 404),
|
||||||
|
(b"/bp2/with/strict/", {}, 200),
|
||||||
|
# bp3 base
|
||||||
|
(b"/bp3", {}, 200),
|
||||||
|
(b"/bp3", {"host": "maybe.com"}, 200),
|
||||||
|
(b"/bp3/host", {"host": "matching.com"}, 200), # BROKEN ON MASTER
|
||||||
|
(b"/bp3/host", {"host": "wrong.com"}, 404),
|
||||||
|
# bp3 strict_slashes default
|
||||||
|
(b"/bp3/without", {}, 200),
|
||||||
|
(b"/bp3/without/", {}, 200),
|
||||||
|
(b"/bp3/with", {}, 200),
|
||||||
|
(b"/bp3/with/", {}, 200),
|
||||||
|
# bp3 strict_slashes off - expressly
|
||||||
|
(b"/bp3/expwithout", {}, 200),
|
||||||
|
(b"/bp3/expwithout/", {}, 200),
|
||||||
|
(b"/bp3/expwith", {}, 200),
|
||||||
|
(b"/bp3/expwith/", {}, 200),
|
||||||
|
# bp3 strict_slashes on
|
||||||
|
(b"/bp3/without/strict", {}, 200),
|
||||||
|
(b"/bp3/without/strict/", {}, 404),
|
||||||
|
(b"/bp3/with/strict", {}, 404),
|
||||||
|
(b"/bp3/with/strict/", {}, 200),
|
||||||
|
# bp4 base
|
||||||
|
(b"/bp4", {}, 404),
|
||||||
|
(b"/bp4", {"host": "maybe.com"}, 200),
|
||||||
|
(b"/bp4/host", {"host": "matching.com"}, 200), # BROKEN ON MASTER
|
||||||
|
(b"/bp4/host", {"host": "wrong.com"}, 404),
|
||||||
|
# bp4 strict_slashes default
|
||||||
|
(b"/bp4/without", {}, 404),
|
||||||
|
(b"/bp4/without/", {}, 404),
|
||||||
|
(b"/bp4/with", {}, 404),
|
||||||
|
(b"/bp4/with/", {}, 404),
|
||||||
|
# bp4 strict_slashes off - expressly
|
||||||
|
(b"/bp4/expwithout", {}, 404),
|
||||||
|
(b"/bp4/expwithout/", {}, 404),
|
||||||
|
(b"/bp4/expwith", {}, 404),
|
||||||
|
(b"/bp4/expwith/", {}, 404),
|
||||||
|
# bp4 strict_slashes on
|
||||||
|
(b"/bp4/without/strict", {}, 404),
|
||||||
|
(b"/bp4/without/strict/", {}, 404),
|
||||||
|
(b"/bp4/with/strict", {}, 404),
|
||||||
|
(b"/bp4/with/strict/", {}, 404),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_matching(path, headers, expected):
|
||||||
|
app = Sanic("dev")
|
||||||
|
bp1 = Blueprint("bp1", url_prefix="/bp1")
|
||||||
|
bp2 = Blueprint("bp2", url_prefix="/bp2", strict_slashes=True)
|
||||||
|
bp3 = Blueprint("bp3", url_prefix="/bp3", strict_slashes=False)
|
||||||
|
bp4 = Blueprint("bp4", url_prefix="/bp4", host="maybe.com")
|
||||||
|
|
||||||
|
def handler(request):
|
||||||
|
return text("Hello!")
|
||||||
|
|
||||||
|
defs = (
|
||||||
|
("/", None, None),
|
||||||
|
("/host", None, "matching.com"),
|
||||||
|
("/without", None, None),
|
||||||
|
("/with/", None, None),
|
||||||
|
("/expwithout", False, None),
|
||||||
|
("/expwith/", False, None),
|
||||||
|
("/without/strict", True, None),
|
||||||
|
("/with/strict/", True, None),
|
||||||
|
)
|
||||||
|
for uri, strict_slashes, host in defs:
|
||||||
|
params = {"uri": uri}
|
||||||
|
if strict_slashes is not None:
|
||||||
|
params["strict_slashes"] = strict_slashes
|
||||||
|
if host is not None:
|
||||||
|
params["host"] = host
|
||||||
|
app.route(**params)(handler)
|
||||||
|
bp1.route(**params)(handler)
|
||||||
|
bp2.route(**params)(handler)
|
||||||
|
bp3.route(**params)(handler)
|
||||||
|
bp4.route(**params)(handler)
|
||||||
|
|
||||||
|
app.blueprint(bp1)
|
||||||
|
app.blueprint(bp2)
|
||||||
|
app.blueprint(bp3)
|
||||||
|
app.blueprint(bp4)
|
||||||
|
|
||||||
|
app.router.finalize()
|
||||||
|
|
||||||
|
request = Request(path, headers, None, "GET", None, app)
|
||||||
|
|
||||||
|
try:
|
||||||
|
app.router.get(request=request)
|
||||||
|
except NotFound:
|
||||||
|
response = 404
|
||||||
|
except Exception:
|
||||||
|
response = 500
|
||||||
|
else:
|
||||||
|
response = 200
|
||||||
|
|
||||||
|
assert response == expected
|
||||||
|
|
||||||
|
|
||||||
|
# # ------------------------------------------------------------ #
|
||||||
|
# # UTF-8
|
||||||
|
# # ------------------------------------------------------------ #
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("method", HTTP_METHODS)
|
@pytest.mark.parametrize("method", HTTP_METHODS)
|
||||||
|
@ -164,7 +326,6 @@ def test_route_optional_slash(app):
|
||||||
|
|
||||||
def test_route_strict_slashes_set_to_false_and_host_is_a_list(app):
|
def test_route_strict_slashes_set_to_false_and_host_is_a_list(app):
|
||||||
# Part of regression test for issue #1120
|
# Part of regression test for issue #1120
|
||||||
|
|
||||||
test_client = SanicTestClient(app, port=42101)
|
test_client = SanicTestClient(app, port=42101)
|
||||||
site1 = f"127.0.0.1:{test_client.port}"
|
site1 = f"127.0.0.1:{test_client.port}"
|
||||||
|
|
||||||
|
@ -176,6 +337,8 @@ def test_route_strict_slashes_set_to_false_and_host_is_a_list(app):
|
||||||
request, response = test_client.get("http://" + site1 + "/get")
|
request, response = test_client.get("http://" + site1 + "/get")
|
||||||
assert response.text == "OK"
|
assert response.text == "OK"
|
||||||
|
|
||||||
|
app.router.finalized = False
|
||||||
|
|
||||||
@app.post("/post", host=[site1, "site2.com"], strict_slashes=False)
|
@app.post("/post", host=[site1, "site2.com"], strict_slashes=False)
|
||||||
def post_handler(request):
|
def post_handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
@ -183,6 +346,8 @@ def test_route_strict_slashes_set_to_false_and_host_is_a_list(app):
|
||||||
request, response = test_client.post("http://" + site1 + "/post")
|
request, response = test_client.post("http://" + site1 + "/post")
|
||||||
assert response.text == "OK"
|
assert response.text == "OK"
|
||||||
|
|
||||||
|
app.router.finalized = False
|
||||||
|
|
||||||
@app.put("/put", host=[site1, "site2.com"], strict_slashes=False)
|
@app.put("/put", host=[site1, "site2.com"], strict_slashes=False)
|
||||||
def put_handler(request):
|
def put_handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
@ -190,6 +355,8 @@ def test_route_strict_slashes_set_to_false_and_host_is_a_list(app):
|
||||||
request, response = test_client.put("http://" + site1 + "/put")
|
request, response = test_client.put("http://" + site1 + "/put")
|
||||||
assert response.text == "OK"
|
assert response.text == "OK"
|
||||||
|
|
||||||
|
app.router.finalized = False
|
||||||
|
|
||||||
@app.delete("/delete", host=[site1, "site2.com"], strict_slashes=False)
|
@app.delete("/delete", host=[site1, "site2.com"], strict_slashes=False)
|
||||||
def delete_handler(request):
|
def delete_handler(request):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
@ -294,6 +461,8 @@ def test_dynamic_route(app):
|
||||||
results.append(name)
|
results.append(name)
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
|
app.router.finalize(False)
|
||||||
|
|
||||||
request, response = app.test_client.get("/folder/test123")
|
request, response = app.test_client.get("/folder/test123")
|
||||||
|
|
||||||
assert response.text == "OK"
|
assert response.text == "OK"
|
||||||
|
@ -368,6 +537,9 @@ def test_dynamic_route_regex(app):
|
||||||
async def handler(request, folder_id):
|
async def handler(request, folder_id):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
|
||||||
|
app.router.finalize()
|
||||||
|
print(app.router.find_route_src)
|
||||||
|
|
||||||
request, response = app.test_client.get("/folder/test")
|
request, response = app.test_client.get("/folder/test")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
@ -415,6 +587,8 @@ def test_dynamic_route_path(app):
|
||||||
request, response = app.test_client.get("/info")
|
request, response = app.test_client.get("/info")
|
||||||
assert response.status == 404
|
assert response.status == 404
|
||||||
|
|
||||||
|
app.router.reset()
|
||||||
|
|
||||||
@app.route("/<path:path>")
|
@app.route("/<path:path>")
|
||||||
async def handler1(request, path):
|
async def handler1(request, path):
|
||||||
return text("OK")
|
return text("OK")
|
||||||
|
@ -774,7 +948,7 @@ def test_removing_slash(app):
|
||||||
def post(_):
|
def post(_):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert len(app.router.routes_all.keys()) == 2
|
assert len(app.router.routes_all.keys()) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_overload_routes(app):
|
def test_overload_routes(app):
|
||||||
|
@ -798,6 +972,7 @@ def test_overload_routes(app):
|
||||||
request, response = app.test_client.delete("/overload")
|
request, response = app.test_client.delete("/overload")
|
||||||
assert response.status == 405
|
assert response.status == 405
|
||||||
|
|
||||||
|
app.router.reset()
|
||||||
with pytest.raises(RouteExists):
|
with pytest.raises(RouteExists):
|
||||||
|
|
||||||
@app.route("/overload", methods=["PUT", "DELETE"])
|
@app.route("/overload", methods=["PUT", "DELETE"])
|
||||||
|
@ -810,11 +985,18 @@ def test_unmergeable_overload_routes(app):
|
||||||
async def handler1(request):
|
async def handler1(request):
|
||||||
return text("OK1")
|
return text("OK1")
|
||||||
|
|
||||||
with pytest.raises(RouteExists):
|
@app.route("/overload_whole", methods=["POST", "PUT"])
|
||||||
|
async def handler2(request):
|
||||||
|
return text("OK1")
|
||||||
|
|
||||||
@app.route("/overload_whole", methods=["POST", "PUT"])
|
assert (
|
||||||
async def handler2(request):
|
len(
|
||||||
return text("Duplicated")
|
dict(list(app.router.static_routes.values())[0].handlers)[
|
||||||
|
"overload_whole"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
== 3
|
||||||
|
)
|
||||||
|
|
||||||
request, response = app.test_client.get("/overload_whole")
|
request, response = app.test_client.get("/overload_whole")
|
||||||
assert response.text == "OK1"
|
assert response.text == "OK1"
|
||||||
|
@ -822,6 +1004,11 @@ def test_unmergeable_overload_routes(app):
|
||||||
request, response = app.test_client.post("/overload_whole")
|
request, response = app.test_client.post("/overload_whole")
|
||||||
assert response.text == "OK1"
|
assert response.text == "OK1"
|
||||||
|
|
||||||
|
request, response = app.test_client.put("/overload_whole")
|
||||||
|
assert response.text == "OK1"
|
||||||
|
|
||||||
|
app.router.reset()
|
||||||
|
|
||||||
@app.route("/overload_part", methods=["GET"])
|
@app.route("/overload_part", methods=["GET"])
|
||||||
async def handler3(request):
|
async def handler3(request):
|
||||||
return text("OK1")
|
return text("OK1")
|
||||||
|
@ -847,7 +1034,9 @@ def test_unicode_routes(app):
|
||||||
request, response = app.test_client.get("/你好")
|
request, response = app.test_client.get("/你好")
|
||||||
assert response.text == "OK1"
|
assert response.text == "OK1"
|
||||||
|
|
||||||
@app.route("/overload/<param>", methods=["GET"])
|
app.router.reset()
|
||||||
|
|
||||||
|
@app.route("/overload/<param>", methods=["GET"], unquote=True)
|
||||||
async def handler2(request, param):
|
async def handler2(request, param):
|
||||||
return text("OK2 " + param)
|
return text("OK2 " + param)
|
||||||
|
|
||||||
|
@ -865,20 +1054,38 @@ def test_uri_with_different_method_and_different_params(app):
|
||||||
return json({"action": action})
|
return json({"action": action})
|
||||||
|
|
||||||
request, response = app.test_client.get("/ads/1234")
|
request, response = app.test_client.get("/ads/1234")
|
||||||
assert response.status == 200
|
assert response.status == 405
|
||||||
assert response.json == {"ad_id": "1234"}
|
|
||||||
|
|
||||||
request, response = app.test_client.post("/ads/post")
|
request, response = app.test_client.post("/ads/post")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.json == {"action": "post"}
|
assert response.json == {"action": "post"}
|
||||||
|
|
||||||
|
|
||||||
def test_route_raise_ParameterNameConflicts(app):
|
def test_uri_with_different_method_and_same_params(app):
|
||||||
with pytest.raises(ParameterNameConflicts):
|
@app.route("/ads/<ad_id>", methods=["GET"])
|
||||||
|
async def ad_get(request, ad_id):
|
||||||
|
return json({"ad_id": ad_id})
|
||||||
|
|
||||||
@app.get("/api/v1/<user>/<user>/")
|
@app.route("/ads/<ad_id>", methods=["POST"])
|
||||||
def handler(request, user):
|
async def ad_post(request, ad_id):
|
||||||
return text("OK")
|
return json({"ad_id": ad_id})
|
||||||
|
|
||||||
|
request, response = app.test_client.get("/ads/1234")
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.json == {"ad_id": "1234"}
|
||||||
|
|
||||||
|
request, response = app.test_client.post("/ads/post")
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.json == {"ad_id": "post"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_route_raise_ParameterNameConflicts(app):
|
||||||
|
@app.get("/api/v1/<user>/<user>/")
|
||||||
|
def handler(request, user):
|
||||||
|
return text("OK")
|
||||||
|
|
||||||
|
with pytest.raises(ParameterNameConflicts):
|
||||||
|
app.router.finalize()
|
||||||
|
|
||||||
|
|
||||||
def test_route_invalid_host(app):
|
def test_route_invalid_host(app):
|
||||||
|
|
|
@ -106,6 +106,7 @@ def test_static_file_bytes(app, static_file_directory, file_name):
|
||||||
[dict(), list(), object()],
|
[dict(), list(), object()],
|
||||||
)
|
)
|
||||||
def test_static_file_invalid_path(app, static_file_directory, file_name):
|
def test_static_file_invalid_path(app, static_file_directory, file_name):
|
||||||
|
app.route("/")(lambda x: x)
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
app.static("/testing.file", file_name)
|
app.static("/testing.file", file_name)
|
||||||
request, response = app.test_client.get("/testing.file")
|
request, response = app.test_client.get("/testing.file")
|
||||||
|
|
|
@ -7,6 +7,7 @@ import pytest as pytest
|
||||||
from sanic_testing.testing import HOST as test_host
|
from sanic_testing.testing import HOST as test_host
|
||||||
from sanic_testing.testing import PORT as test_port
|
from sanic_testing.testing import PORT as test_port
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
from sanic.exceptions import URLBuildError
|
from sanic.exceptions import URLBuildError
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
|
@ -98,36 +99,36 @@ def test_url_for_with_server_name(app):
|
||||||
assert response.text == "this should pass"
|
assert response.text == "this should pass"
|
||||||
|
|
||||||
|
|
||||||
def test_fails_if_endpoint_not_found(app):
|
def test_fails_if_endpoint_not_found():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.route("/fail")
|
@app.route("/fail")
|
||||||
def fail(request):
|
def fail(request):
|
||||||
return text("this should fail")
|
return text("this should fail")
|
||||||
|
|
||||||
with pytest.raises(URLBuildError) as e:
|
with pytest.raises(URLBuildError) as e:
|
||||||
app.url_for("passes")
|
app.url_for("passes")
|
||||||
|
e.match("Endpoint with name `app.passes` was not found")
|
||||||
assert str(e.value) == "Endpoint with name `passes` was not found"
|
|
||||||
|
|
||||||
|
|
||||||
def test_fails_url_build_if_param_not_passed(app):
|
def test_fails_url_build_if_param_not_passed(app):
|
||||||
url = "/"
|
url = "/"
|
||||||
|
|
||||||
for letter in string.ascii_letters:
|
for letter in string.ascii_lowercase:
|
||||||
url += f"<{letter}>/"
|
url += f"<{letter}>/"
|
||||||
|
|
||||||
@app.route(url)
|
@app.route(url)
|
||||||
def fail(request):
|
def fail(request):
|
||||||
return text("this should fail")
|
return text("this should fail")
|
||||||
|
|
||||||
fail_args = list(string.ascii_letters)
|
fail_args = list(string.ascii_lowercase)
|
||||||
fail_args.pop()
|
fail_args.pop()
|
||||||
|
|
||||||
fail_kwargs = {l: l for l in fail_args}
|
fail_kwargs = {l: l for l in fail_args}
|
||||||
|
|
||||||
with pytest.raises(URLBuildError) as e:
|
with pytest.raises(URLBuildError) as e:
|
||||||
app.url_for("fail", **fail_kwargs)
|
app.url_for("fail", **fail_kwargs)
|
||||||
|
assert e.match("Required parameter `z` was not passed to url_for")
|
||||||
assert "Required parameter `Z` was not passed to url_for" in str(e.value)
|
|
||||||
|
|
||||||
|
|
||||||
def test_fails_url_build_if_params_not_passed(app):
|
def test_fails_url_build_if_params_not_passed(app):
|
||||||
|
@ -137,8 +138,7 @@ def test_fails_url_build_if_params_not_passed(app):
|
||||||
|
|
||||||
with pytest.raises(ValueError) as e:
|
with pytest.raises(ValueError) as e:
|
||||||
app.url_for("fail", _scheme="http")
|
app.url_for("fail", _scheme="http")
|
||||||
|
assert e.match("When specifying _scheme, _external must be True")
|
||||||
assert str(e.value) == "When specifying _scheme, _external must be True"
|
|
||||||
|
|
||||||
|
|
||||||
COMPLEX_PARAM_URL = (
|
COMPLEX_PARAM_URL = (
|
||||||
|
@ -168,7 +168,7 @@ def test_fails_with_int_message(app):
|
||||||
|
|
||||||
expected_error = (
|
expected_error = (
|
||||||
r'Value "not_int" for parameter `foo` '
|
r'Value "not_int" for parameter `foo` '
|
||||||
r"does not match pattern for type `int`: -?\d+"
|
r"does not match pattern for type `int`: ^-?\d+"
|
||||||
)
|
)
|
||||||
assert str(e.value) == expected_error
|
assert str(e.value) == expected_error
|
||||||
|
|
||||||
|
@ -199,13 +199,10 @@ def test_fails_with_two_letter_string_message(app):
|
||||||
|
|
||||||
with pytest.raises(URLBuildError) as e:
|
with pytest.raises(URLBuildError) as e:
|
||||||
app.url_for("fail", **failing_kwargs)
|
app.url_for("fail", **failing_kwargs)
|
||||||
|
e.match(
|
||||||
expected_error = (
|
'Value "foobar" for parameter `two_letter_string` '
|
||||||
'Value "foobar" for parameter `two_letter_string` '
|
"does not satisfy pattern ^[A-z]{2}$"
|
||||||
"does not satisfy pattern [A-z]{2}"
|
)
|
||||||
)
|
|
||||||
|
|
||||||
assert str(e.value) == expected_error
|
|
||||||
|
|
||||||
|
|
||||||
def test_fails_with_number_message(app):
|
def test_fails_with_number_message(app):
|
||||||
|
@ -218,13 +215,10 @@ def test_fails_with_number_message(app):
|
||||||
|
|
||||||
with pytest.raises(URLBuildError) as e:
|
with pytest.raises(URLBuildError) as e:
|
||||||
app.url_for("fail", **failing_kwargs)
|
app.url_for("fail", **failing_kwargs)
|
||||||
|
e.match(
|
||||||
expected_error = (
|
'Value "foo" for parameter `some_number` '
|
||||||
'Value "foo" for parameter `some_number` '
|
r"does not match pattern for type `float`: ^-?(?:\d+(?:\.\d*)?|\.\d+)$"
|
||||||
r"does not match pattern for type `float`: -?(?:\d+(?:\.\d*)?|\.\d+)"
|
)
|
||||||
)
|
|
||||||
|
|
||||||
assert str(e.value) == expected_error
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("number", [3, -3, 13.123, -13.123])
|
@pytest.mark.parametrize("number", [3, -3, 13.123, -13.123])
|
||||||
|
@ -259,7 +253,8 @@ def test_adds_other_supplied_values_as_query_string(app):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def blueprint_app(app):
|
def blueprint_app():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
first_print = Blueprint("first", url_prefix="/first")
|
first_print = Blueprint("first", url_prefix="/first")
|
||||||
second_print = Blueprint("second", url_prefix="/second")
|
second_print = Blueprint("second", url_prefix="/second")
|
||||||
|
@ -273,11 +268,11 @@ def blueprint_app(app):
|
||||||
return text(f"foo from first : {param}")
|
return text(f"foo from first : {param}")
|
||||||
|
|
||||||
@second_print.route("/foo") # noqa
|
@second_print.route("/foo") # noqa
|
||||||
def foo(request):
|
def bar(request):
|
||||||
return text("foo from second")
|
return text("foo from second")
|
||||||
|
|
||||||
@second_print.route("/foo/<param>") # noqa
|
@second_print.route("/foo/<param>") # noqa
|
||||||
def foo_with_param(request, param):
|
def bar_with_param(request, param):
|
||||||
return text(f"foo from second : {param}")
|
return text(f"foo from second : {param}")
|
||||||
|
|
||||||
app.blueprint(first_print)
|
app.blueprint(first_print)
|
||||||
|
@ -290,7 +285,7 @@ def test_blueprints_are_named_correctly(blueprint_app):
|
||||||
first_url = blueprint_app.url_for("first.foo")
|
first_url = blueprint_app.url_for("first.foo")
|
||||||
assert first_url == "/first/foo"
|
assert first_url == "/first/foo"
|
||||||
|
|
||||||
second_url = blueprint_app.url_for("second.foo")
|
second_url = blueprint_app.url_for("second.bar")
|
||||||
assert second_url == "/second/foo"
|
assert second_url == "/second/foo"
|
||||||
|
|
||||||
|
|
||||||
|
@ -298,7 +293,7 @@ def test_blueprints_work_with_params(blueprint_app):
|
||||||
first_url = blueprint_app.url_for("first.foo_with_param", param="bar")
|
first_url = blueprint_app.url_for("first.foo_with_param", param="bar")
|
||||||
assert first_url == "/first/foo/bar"
|
assert first_url == "/first/foo/bar"
|
||||||
|
|
||||||
second_url = blueprint_app.url_for("second.foo_with_param", param="bar")
|
second_url = blueprint_app.url_for("second.bar_with_param", param="bar")
|
||||||
assert second_url == "/second/foo/bar"
|
assert second_url == "/second/foo/bar"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from sanic_testing.testing import SanicTestClient
|
from sanic_testing.testing import SanicTestClient
|
||||||
|
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
|
|
||||||
|
|
||||||
def test_routes_with_host(app):
|
def test_routes_with_host(app):
|
||||||
@app.route("/")
|
|
||||||
@app.route("/", name="hostindex", host="example.com")
|
@app.route("/", name="hostindex", host="example.com")
|
||||||
@app.route("/path", name="hostpath", host="path.example.com")
|
@app.route("/path", name="hostpath", host="path.example.com")
|
||||||
def index(request):
|
def index(request):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
assert app.url_for("index") == "/"
|
|
||||||
assert app.url_for("hostindex") == "/"
|
assert app.url_for("hostindex") == "/"
|
||||||
assert app.url_for("hostpath") == "/path"
|
assert app.url_for("hostpath") == "/path"
|
||||||
assert app.url_for("hostindex", _external=True) == "http://example.com/"
|
assert app.url_for("hostindex", _external=True) == "http://example.com/"
|
||||||
|
@ -22,6 +22,27 @@ def test_routes_with_host(app):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_routes_with_multiple_hosts(app):
|
||||||
|
@app.route("/", name="hostindex", host=["example.com", "path.example.com"])
|
||||||
|
def index(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert app.url_for("hostindex") == "/"
|
||||||
|
assert (
|
||||||
|
app.url_for("hostindex", _host="example.com") == "http://example.com/"
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as e:
|
||||||
|
assert app.url_for("hostindex", _external=True)
|
||||||
|
assert str(e.value).startswith("Host is ambiguous")
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as e:
|
||||||
|
assert app.url_for("hostindex", _host="unknown.com")
|
||||||
|
assert str(e.value).startswith(
|
||||||
|
"Requested host (unknown.com) is not available for this route"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_websocket_bp_route_name(app):
|
def test_websocket_bp_route_name(app):
|
||||||
"""Tests that blueprint websocket route is named."""
|
"""Tests that blueprint websocket route is named."""
|
||||||
event = asyncio.Event()
|
event = asyncio.Event()
|
||||||
|
@ -63,3 +84,7 @@ def test_websocket_bp_route_name(app):
|
||||||
|
|
||||||
uri = app.url_for("test_bp.foobar_3")
|
uri = app.url_for("test_bp.foobar_3")
|
||||||
assert uri == "/bp/route3"
|
assert uri == "/bp/route3"
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: add test with a route with multiple hosts
|
||||||
|
# TODO: add test with a route with _host in url_for
|
||||||
|
|
|
@ -3,6 +3,7 @@ import os
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,9 +27,15 @@ def get_file_content(static_file_directory, file_name):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"file_name", ["test.file", "decode me.txt", "python.png"]
|
"file_name",
|
||||||
|
[
|
||||||
|
"test.file",
|
||||||
|
"decode me.txt",
|
||||||
|
"python.png",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
def test_static_file(app, static_file_directory, file_name):
|
def test_static_file(static_file_directory, file_name):
|
||||||
|
app = Sanic("qq")
|
||||||
app.static(
|
app.static(
|
||||||
"/testing.file", get_file_path(static_file_directory, file_name)
|
"/testing.file", get_file_path(static_file_directory, file_name)
|
||||||
)
|
)
|
||||||
|
@ -38,6 +45,8 @@ def test_static_file(app, static_file_directory, file_name):
|
||||||
name="testing_file",
|
name="testing_file",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app.router.finalize()
|
||||||
|
|
||||||
uri = app.url_for("static")
|
uri = app.url_for("static")
|
||||||
uri2 = app.url_for("static", filename="any")
|
uri2 = app.url_for("static", filename="any")
|
||||||
uri3 = app.url_for("static", name="static", filename="any")
|
uri3 = app.url_for("static", name="static", filename="any")
|
||||||
|
@ -46,10 +55,14 @@ def test_static_file(app, static_file_directory, file_name):
|
||||||
assert uri == uri2
|
assert uri == uri2
|
||||||
assert uri2 == uri3
|
assert uri2 == uri3
|
||||||
|
|
||||||
|
app.router.reset()
|
||||||
|
|
||||||
request, response = app.test_client.get(uri)
|
request, response = app.test_client.get(uri)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.body == get_file_content(static_file_directory, file_name)
|
assert response.body == get_file_content(static_file_directory, file_name)
|
||||||
|
|
||||||
|
app.router.reset()
|
||||||
|
|
||||||
bp = Blueprint("test_bp_static", url_prefix="/bp")
|
bp = Blueprint("test_bp_static", url_prefix="/bp")
|
||||||
|
|
||||||
bp.static("/testing.file", get_file_path(static_file_directory, file_name))
|
bp.static("/testing.file", get_file_path(static_file_directory, file_name))
|
||||||
|
@ -61,19 +74,14 @@ def test_static_file(app, static_file_directory, file_name):
|
||||||
|
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
|
|
||||||
uri = app.url_for("static", name="test_bp_static.static")
|
uris = [
|
||||||
uri2 = app.url_for("static", name="test_bp_static.static", filename="any")
|
app.url_for("static", name="test_bp_static.static"),
|
||||||
uri3 = app.url_for("test_bp_static.static")
|
app.url_for("static", name="test_bp_static.static", filename="any"),
|
||||||
uri4 = app.url_for("test_bp_static.static", name="any")
|
app.url_for("test_bp_static.static"),
|
||||||
uri5 = app.url_for("test_bp_static.static", filename="any")
|
app.url_for("test_bp_static.static", filename="any"),
|
||||||
uri6 = app.url_for("test_bp_static.static", name="any", filename="any")
|
]
|
||||||
|
|
||||||
assert uri == "/bp/testing.file"
|
assert all(uri == "/bp/testing.file" for uri in uris)
|
||||||
assert uri == uri2
|
|
||||||
assert uri2 == uri3
|
|
||||||
assert uri3 == uri4
|
|
||||||
assert uri4 == uri5
|
|
||||||
assert uri5 == uri6
|
|
||||||
|
|
||||||
request, response = app.test_client.get(uri)
|
request, response = app.test_client.get(uri)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
@ -112,7 +120,9 @@ def test_static_file(app, static_file_directory, file_name):
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
||||||
@pytest.mark.parametrize("base_uri", ["/static", "", "/dir"])
|
@pytest.mark.parametrize("base_uri", ["/static", "", "/dir"])
|
||||||
def test_static_directory(app, file_name, base_uri, static_file_directory):
|
def test_static_directory(file_name, base_uri, static_file_directory):
|
||||||
|
app = Sanic("base")
|
||||||
|
|
||||||
app.static(base_uri, static_file_directory)
|
app.static(base_uri, static_file_directory)
|
||||||
base_uri2 = base_uri + "/2"
|
base_uri2 = base_uri + "/2"
|
||||||
app.static(base_uri2, static_file_directory, name="uploads")
|
app.static(base_uri2, static_file_directory, name="uploads")
|
||||||
|
@ -141,6 +151,8 @@ def test_static_directory(app, file_name, base_uri, static_file_directory):
|
||||||
|
|
||||||
bp.static(base_uri, static_file_directory)
|
bp.static(base_uri, static_file_directory)
|
||||||
bp.static(base_uri2, static_file_directory, name="uploads")
|
bp.static(base_uri2, static_file_directory, name="uploads")
|
||||||
|
|
||||||
|
app.router.reset()
|
||||||
app.blueprint(bp)
|
app.blueprint(bp)
|
||||||
|
|
||||||
uri = app.url_for(
|
uri = app.url_for(
|
||||||
|
@ -169,7 +181,8 @@ def test_static_directory(app, file_name, base_uri, static_file_directory):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
||||||
def test_static_head_request(app, file_name, static_file_directory):
|
def test_static_head_request(file_name, static_file_directory):
|
||||||
|
app = Sanic("base")
|
||||||
app.static(
|
app.static(
|
||||||
"/testing.file",
|
"/testing.file",
|
||||||
get_file_path(static_file_directory, file_name),
|
get_file_path(static_file_directory, file_name),
|
||||||
|
@ -214,7 +227,8 @@ def test_static_head_request(app, file_name, static_file_directory):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
||||||
def test_static_content_range_correct(app, file_name, static_file_directory):
|
def test_static_content_range_correct(file_name, static_file_directory):
|
||||||
|
app = Sanic("base")
|
||||||
app.static(
|
app.static(
|
||||||
"/testing.file",
|
"/testing.file",
|
||||||
get_file_path(static_file_directory, file_name),
|
get_file_path(static_file_directory, file_name),
|
||||||
|
@ -252,11 +266,6 @@ def test_static_content_range_correct(app, file_name, static_file_directory):
|
||||||
"static", name="test_bp_static.static", filename="any"
|
"static", name="test_bp_static.static", filename="any"
|
||||||
)
|
)
|
||||||
assert uri == app.url_for("test_bp_static.static")
|
assert uri == app.url_for("test_bp_static.static")
|
||||||
assert uri == app.url_for("test_bp_static.static", name="any")
|
|
||||||
assert uri == app.url_for("test_bp_static.static", filename="any")
|
|
||||||
assert uri == app.url_for(
|
|
||||||
"test_bp_static.static", name="any", filename="any"
|
|
||||||
)
|
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 206
|
assert response.status == 206
|
||||||
|
@ -270,7 +279,8 @@ def test_static_content_range_correct(app, file_name, static_file_directory):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
||||||
def test_static_content_range_front(app, file_name, static_file_directory):
|
def test_static_content_range_front(file_name, static_file_directory):
|
||||||
|
app = Sanic("base")
|
||||||
app.static(
|
app.static(
|
||||||
"/testing.file",
|
"/testing.file",
|
||||||
get_file_path(static_file_directory, file_name),
|
get_file_path(static_file_directory, file_name),
|
||||||
|
@ -308,11 +318,7 @@ def test_static_content_range_front(app, file_name, static_file_directory):
|
||||||
"static", name="test_bp_static.static", filename="any"
|
"static", name="test_bp_static.static", filename="any"
|
||||||
)
|
)
|
||||||
assert uri == app.url_for("test_bp_static.static")
|
assert uri == app.url_for("test_bp_static.static")
|
||||||
assert uri == app.url_for("test_bp_static.static", name="any")
|
|
||||||
assert uri == app.url_for("test_bp_static.static", filename="any")
|
assert uri == app.url_for("test_bp_static.static", filename="any")
|
||||||
assert uri == app.url_for(
|
|
||||||
"test_bp_static.static", name="any", filename="any"
|
|
||||||
)
|
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 206
|
assert response.status == 206
|
||||||
|
@ -326,7 +332,8 @@ def test_static_content_range_front(app, file_name, static_file_directory):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
||||||
def test_static_content_range_back(app, file_name, static_file_directory):
|
def test_static_content_range_back(file_name, static_file_directory):
|
||||||
|
app = Sanic("base")
|
||||||
app.static(
|
app.static(
|
||||||
"/testing.file",
|
"/testing.file",
|
||||||
get_file_path(static_file_directory, file_name),
|
get_file_path(static_file_directory, file_name),
|
||||||
|
@ -364,11 +371,7 @@ def test_static_content_range_back(app, file_name, static_file_directory):
|
||||||
"static", name="test_bp_static.static", filename="any"
|
"static", name="test_bp_static.static", filename="any"
|
||||||
)
|
)
|
||||||
assert uri == app.url_for("test_bp_static.static")
|
assert uri == app.url_for("test_bp_static.static")
|
||||||
assert uri == app.url_for("test_bp_static.static", name="any")
|
|
||||||
assert uri == app.url_for("test_bp_static.static", filename="any")
|
assert uri == app.url_for("test_bp_static.static", filename="any")
|
||||||
assert uri == app.url_for(
|
|
||||||
"test_bp_static.static", name="any", filename="any"
|
|
||||||
)
|
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 206
|
assert response.status == 206
|
||||||
|
@ -382,7 +385,8 @@ def test_static_content_range_back(app, file_name, static_file_directory):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
||||||
def test_static_content_range_empty(app, file_name, static_file_directory):
|
def test_static_content_range_empty(file_name, static_file_directory):
|
||||||
|
app = Sanic("base")
|
||||||
app.static(
|
app.static(
|
||||||
"/testing.file",
|
"/testing.file",
|
||||||
get_file_path(static_file_directory, file_name),
|
get_file_path(static_file_directory, file_name),
|
||||||
|
@ -420,11 +424,7 @@ def test_static_content_range_empty(app, file_name, static_file_directory):
|
||||||
"static", name="test_bp_static.static", filename="any"
|
"static", name="test_bp_static.static", filename="any"
|
||||||
)
|
)
|
||||||
assert uri == app.url_for("test_bp_static.static")
|
assert uri == app.url_for("test_bp_static.static")
|
||||||
assert uri == app.url_for("test_bp_static.static", name="any")
|
|
||||||
assert uri == app.url_for("test_bp_static.static", filename="any")
|
assert uri == app.url_for("test_bp_static.static", filename="any")
|
||||||
assert uri == app.url_for(
|
|
||||||
"test_bp_static.static", name="any", filename="any"
|
|
||||||
)
|
|
||||||
|
|
||||||
request, response = app.test_client.get(uri)
|
request, response = app.test_client.get(uri)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
@ -440,6 +440,7 @@ def test_static_content_range_empty(app, file_name, static_file_directory):
|
||||||
|
|
||||||
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
@pytest.mark.parametrize("file_name", ["test.file", "decode me.txt"])
|
||||||
def test_static_content_range_error(app, file_name, static_file_directory):
|
def test_static_content_range_error(app, file_name, static_file_directory):
|
||||||
|
app = Sanic("base")
|
||||||
app.static(
|
app.static(
|
||||||
"/testing.file",
|
"/testing.file",
|
||||||
get_file_path(static_file_directory, file_name),
|
get_file_path(static_file_directory, file_name),
|
||||||
|
@ -475,11 +476,7 @@ def test_static_content_range_error(app, file_name, static_file_directory):
|
||||||
"static", name="test_bp_static.static", filename="any"
|
"static", name="test_bp_static.static", filename="any"
|
||||||
)
|
)
|
||||||
assert uri == app.url_for("test_bp_static.static")
|
assert uri == app.url_for("test_bp_static.static")
|
||||||
assert uri == app.url_for("test_bp_static.static", name="any")
|
|
||||||
assert uri == app.url_for("test_bp_static.static", filename="any")
|
assert uri == app.url_for("test_bp_static.static", filename="any")
|
||||||
assert uri == app.url_for(
|
|
||||||
"test_bp_static.static", name="any", filename="any"
|
|
||||||
)
|
|
||||||
|
|
||||||
request, response = app.test_client.get(uri, headers=headers)
|
request, response = app.test_client.get(uri, headers=headers)
|
||||||
assert response.status == 416
|
assert response.status == 416
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from sanic_routing.exceptions import RouteExists
|
||||||
|
|
||||||
|
from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
|
|
||||||
|
|
||||||
def test_vhosts(app):
|
def test_vhosts():
|
||||||
|
app = Sanic("app")
|
||||||
|
|
||||||
@app.route("/", host="example.com")
|
@app.route("/", host="example.com")
|
||||||
async def handler1(request):
|
async def handler1(request):
|
||||||
return text("You're at example.com!")
|
return text("You're at example.com!")
|
||||||
|
@ -38,13 +45,12 @@ def test_vhosts_with_defaults(app):
|
||||||
async def handler1(request):
|
async def handler1(request):
|
||||||
return text("Hello, world!")
|
return text("Hello, world!")
|
||||||
|
|
||||||
@app.route("/")
|
with pytest.raises(RouteExists):
|
||||||
async def handler2(request):
|
|
||||||
return text("default")
|
@app.route("/")
|
||||||
|
async def handler2(request):
|
||||||
|
return text("default")
|
||||||
|
|
||||||
headers = {"Host": "hello.com"}
|
headers = {"Host": "hello.com"}
|
||||||
request, response = app.test_client.get("/", headers=headers)
|
request, response = app.test_client.get("/", headers=headers)
|
||||||
assert response.text == "Hello, world!"
|
assert response.text == "Hello, world!"
|
||||||
|
|
||||||
request, response = app.test_client.get("/")
|
|
||||||
assert response.text == "default"
|
|
||||||
|
|
|
@ -45,9 +45,9 @@ def test_unexisting_methods(app):
|
||||||
|
|
||||||
app.add_route(DummyView.as_view(), "/")
|
app.add_route(DummyView.as_view(), "/")
|
||||||
request, response = app.test_client.get("/")
|
request, response = app.test_client.get("/")
|
||||||
assert response.text == "I am get method"
|
assert response.body == b"I am get method"
|
||||||
request, response = app.test_client.post("/")
|
request, response = app.test_client.post("/")
|
||||||
assert "Method POST not allowed for URL /" in response.text
|
assert b"Method POST not allowed for URL /" in response.body
|
||||||
|
|
||||||
|
|
||||||
def test_argument_methods(app):
|
def test_argument_methods(app):
|
||||||
|
@ -215,17 +215,18 @@ def test_composition_view_runs_methods_as_expected(app, method):
|
||||||
|
|
||||||
if method in ["GET", "POST", "PUT"]:
|
if method in ["GET", "POST", "PUT"]:
|
||||||
request, response = getattr(app.test_client, method.lower())("/")
|
request, response = getattr(app.test_client, method.lower())("/")
|
||||||
|
assert response.status == 200
|
||||||
assert response.text == "first method"
|
assert response.text == "first method"
|
||||||
|
|
||||||
response = view(request)
|
# response = view(request)
|
||||||
assert response.body.decode() == "first method"
|
# assert response.body.decode() == "first method"
|
||||||
|
|
||||||
if method in ["DELETE", "PATCH"]:
|
# if method in ["DELETE", "PATCH"]:
|
||||||
request, response = getattr(app.test_client, method.lower())("/")
|
# request, response = getattr(app.test_client, method.lower())("/")
|
||||||
assert response.text == "second method"
|
# assert response.text == "second method"
|
||||||
|
|
||||||
response = view(request)
|
# response = view(request)
|
||||||
assert response.body.decode() == "second method"
|
# assert response.body.decode() == "second method"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("method", HTTP_METHODS)
|
@pytest.mark.parametrize("method", HTTP_METHODS)
|
||||||
|
|
|
@ -9,6 +9,8 @@ from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from sanic_testing.testing import ASGI_PORT as PORT
|
||||||
|
|
||||||
from sanic.app import Sanic
|
from sanic.app import Sanic
|
||||||
from sanic.worker import GunicornWorker
|
from sanic.worker import GunicornWorker
|
||||||
|
|
||||||
|
@ -17,7 +19,7 @@ from sanic.worker import GunicornWorker
|
||||||
def gunicorn_worker():
|
def gunicorn_worker():
|
||||||
command = (
|
command = (
|
||||||
"gunicorn "
|
"gunicorn "
|
||||||
"--bind 127.0.0.1:1337 "
|
f"--bind 127.0.0.1:{PORT} "
|
||||||
"--worker-class sanic.worker.GunicornWorker "
|
"--worker-class sanic.worker.GunicornWorker "
|
||||||
"examples.simple_server:app"
|
"examples.simple_server:app"
|
||||||
)
|
)
|
||||||
|
@ -31,7 +33,7 @@ def gunicorn_worker():
|
||||||
def gunicorn_worker_with_access_logs():
|
def gunicorn_worker_with_access_logs():
|
||||||
command = (
|
command = (
|
||||||
"gunicorn "
|
"gunicorn "
|
||||||
"--bind 127.0.0.1:1338 "
|
f"--bind 127.0.0.1:{PORT + 1} "
|
||||||
"--worker-class sanic.worker.GunicornWorker "
|
"--worker-class sanic.worker.GunicornWorker "
|
||||||
"examples.simple_server:app"
|
"examples.simple_server:app"
|
||||||
)
|
)
|
||||||
|
@ -45,7 +47,7 @@ def gunicorn_worker_with_env_var():
|
||||||
command = (
|
command = (
|
||||||
'env SANIC_ACCESS_LOG="False" '
|
'env SANIC_ACCESS_LOG="False" '
|
||||||
"gunicorn "
|
"gunicorn "
|
||||||
"--bind 127.0.0.1:1339 "
|
f"--bind 127.0.0.1:{PORT + 2} "
|
||||||
"--worker-class sanic.worker.GunicornWorker "
|
"--worker-class sanic.worker.GunicornWorker "
|
||||||
"--log-level info "
|
"--log-level info "
|
||||||
"examples.simple_server:app"
|
"examples.simple_server:app"
|
||||||
|
@ -56,7 +58,7 @@ def gunicorn_worker_with_env_var():
|
||||||
|
|
||||||
|
|
||||||
def test_gunicorn_worker(gunicorn_worker):
|
def test_gunicorn_worker(gunicorn_worker):
|
||||||
with urllib.request.urlopen("http://localhost:1337/") as f:
|
with urllib.request.urlopen(f"http://localhost:{PORT}/") as f:
|
||||||
res = json.loads(f.read(100).decode())
|
res = json.loads(f.read(100).decode())
|
||||||
assert res["test"]
|
assert res["test"]
|
||||||
|
|
||||||
|
@ -65,7 +67,7 @@ def test_gunicorn_worker_no_logs(gunicorn_worker_with_env_var):
|
||||||
"""
|
"""
|
||||||
if SANIC_ACCESS_LOG was set to False do not show access logs
|
if SANIC_ACCESS_LOG was set to False do not show access logs
|
||||||
"""
|
"""
|
||||||
with urllib.request.urlopen("http://localhost:1339/") as _:
|
with urllib.request.urlopen(f"http://localhost:{PORT + 2}/") as _:
|
||||||
gunicorn_worker_with_env_var.kill()
|
gunicorn_worker_with_env_var.kill()
|
||||||
assert not gunicorn_worker_with_env_var.stdout.read()
|
assert not gunicorn_worker_with_env_var.stdout.read()
|
||||||
|
|
||||||
|
@ -74,7 +76,7 @@ def test_gunicorn_worker_with_logs(gunicorn_worker_with_access_logs):
|
||||||
"""
|
"""
|
||||||
default - show access logs
|
default - show access logs
|
||||||
"""
|
"""
|
||||||
with urllib.request.urlopen("http://localhost:1338/") as _:
|
with urllib.request.urlopen(f"http://localhost:{PORT + 1}/") as _:
|
||||||
gunicorn_worker_with_access_logs.kill()
|
gunicorn_worker_with_access_logs.kill()
|
||||||
assert (
|
assert (
|
||||||
b"(sanic.access)[INFO][127.0.0.1"
|
b"(sanic.access)[INFO][127.0.0.1"
|
||||||
|
|
4
tox.ini
4
tox.ini
|
@ -7,7 +7,7 @@ setenv =
|
||||||
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UJSON=1
|
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UJSON=1
|
||||||
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UVLOOP=1
|
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UVLOOP=1
|
||||||
deps =
|
deps =
|
||||||
sanic-testing==0.1.2
|
sanic-testing
|
||||||
coverage==5.3
|
coverage==5.3
|
||||||
pytest==5.2.1
|
pytest==5.2.1
|
||||||
pytest-cov
|
pytest-cov
|
||||||
|
@ -35,7 +35,7 @@ deps =
|
||||||
commands =
|
commands =
|
||||||
flake8 sanic
|
flake8 sanic
|
||||||
black --config ./.black.toml --check --verbose sanic/
|
black --config ./.black.toml --check --verbose sanic/
|
||||||
isort --check-only sanic
|
isort --check-only sanic --profile=black
|
||||||
|
|
||||||
[testenv:type-checking]
|
[testenv:type-checking]
|
||||||
deps =
|
deps =
|
||||||
|
|
Loading…
Reference in New Issue
Block a user