Resolve some more tests

This commit is contained in:
Adam Hopkins 2021-02-07 11:38:37 +02:00
parent a434ffa8b7
commit c08b153cee
14 changed files with 1052 additions and 749 deletions

View File

@ -16,7 +16,7 @@ from urllib.parse import urlencode, urlunparse
from sanic_routing.route import Route from sanic_routing.route import Route
from sanic import reloader_helpers, websocket from sanic import reloader_helpers
from sanic.asgi import ASGIApp from sanic.asgi import ASGIApp
from sanic.base import BaseSanic from sanic.base import BaseSanic
from sanic.blueprint_group import BlueprintGroup from sanic.blueprint_group import BlueprintGroup
@ -114,6 +114,8 @@ class Sanic(BaseSanic):
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):
"""Synonymous with asyncio.get_event_loop(). """Synonymous with asyncio.get_event_loop().
@ -230,7 +232,6 @@ class Sanic(BaseSanic):
websocket = params.pop("websocket", False) websocket = params.pop("websocket", False)
subprotocols = params.pop("subprotocols", None) subprotocols = params.pop("subprotocols", None)
if websocket: if websocket:
self.enable_websocket() self.enable_websocket()
websocket_handler = partial( websocket_handler = partial(
@ -294,6 +295,12 @@ class Sanic(BaseSanic):
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):
@ -319,30 +326,28 @@ class Sanic(BaseSanic):
# 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]
@ -350,22 +355,36 @@ class Sanic(BaseSanic):
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")
@ -383,45 +402,49 @@ class Sanic(BaseSanic):
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)
# # we only want to match against each individual parameter route.finalize_params()
# specific_pattern = f"^{pattern}$" for params in route.params.values():
# supplied_param = None # name, _type, pattern = self.router.parse_parameter_string(match)
# we only want to match against each individual parameter
# if name in kwargs: for idx, param_info in enumerate(params):
# supplied_param = kwargs.get(name) try:
# del kwargs[name] supplied_param = str(kwargs.pop(param_info.name))
# else: except KeyError:
# 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 idx + 1 == len(params):
if param_info.cast != str:
msg = (
f'Value "{supplied_param}" '
f"for parameter `{param_info.name}` does "
"not match pattern for type "
f"`{param_info.cast.__name__}`: "
f"{param_info.pattern.pattern}"
)
else:
msg = (
f'Value "{supplied_param}" for parameter '
f"`{param_info.name}` does not satisfy "
f"pattern {param_info.pattern.pattern}"
)
raise URLBuildError(msg)
else:
continue
# if not passes_pattern: # replace the parameter in the URL with the supplied value
# if _type != str: replacement_regex = f"(<{param_info.name}.*?>)"
# type_name = _type.__name__ out = re.sub(replacement_regex, supplied_param, out)
# msg = (
# f'Value "{supplied_param}" '
# f"for parameter `{name}` does not "
# f"match pattern for type `{type_name}`: {pattern}"
# )
# else:
# msg = (
# f'Value "{supplied_param}" for parameter `{name}` '
# f"does not satisfy pattern {pattern}"
# )
# raise URLBuildError(msg)
# # replace the parameter in the URL with the supplied value
# replacement_regex = f"(<{name}.*?>)"
# 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
query_string = urlencode(kwargs, doseq=True) if kwargs else "" query_string = urlencode(kwargs, doseq=True) if kwargs else ""
@ -845,9 +868,6 @@ class Sanic(BaseSanic):
await result await result
async def _run_request_middleware(self, request, request_name=None): async def _run_request_middleware(self, request, request_name=None):
print(self.request_middleware)
print(self.named_request_middleware)
print(request_name)
# The if improves speed. I don't know why # The if improves speed. I don't know why
named_middleware = self.named_request_middleware.get( named_middleware = self.named_request_middleware.get(
request_name, deque() request_name, deque()

View File

@ -109,22 +109,35 @@ class Blueprint(BaseSanic):
# 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
)
print(uri, strict_slashes)
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, future.name,
future.ignore_body, future.ignore_body,
future.websocket, future.websocket,
future.subprotocols, future.subprotocols,
future.unquote,
future.static,
) )
route = app._apply_route(apply_route) route = app._apply_route(apply_route)
operation = routes.extend if isinstance(route, list) else routes.append operation = (
routes.extend if isinstance(route, list) else routes.append
)
operation(route) operation(route)
# Static Files # Static Files
@ -149,6 +162,3 @@ class Blueprint(BaseSanic):
# 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__}"

View File

@ -36,6 +36,8 @@ class RouteMixin:
apply=True, apply=True,
subprotocols=None, subprotocols=None,
websocket=False, websocket=False,
unquote=False,
static=False,
): ):
"""Create a blueprint route from a decorated function. """Create a blueprint route from a decorated function.
@ -74,21 +76,28 @@ 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
# TODO: name = self._generate_name(name, handler)
# - THink this thru.... do we want all routes namespaced?
# -
name = self._generate_name(handler, name)
if isinstance(host, str): if isinstance(host, str):
host = frozenset([host]) host = frozenset([host])
elif host and not isinstance(host, frozenset): elif host and not isinstance(host, frozenset):
host = frozenset(host) 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,
@ -102,6 +111,8 @@ class RouteMixin:
ignore_body, ignore_body,
websocket, websocket,
subprotocols, subprotocols,
unquote,
static,
) )
self._future_routes.add(route) self._future_routes.add(route)
@ -499,12 +510,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,
@ -522,5 +537,25 @@ 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:
continue
else:
break
if not name:
raise Exception("...")
if not name.startswith(f"{self.name}."):
name = f"{self.name}.{name}"
return name

View File

@ -15,6 +15,8 @@ FutureRoute = namedtuple(
"ignore_body", "ignore_body",
"websocket", "websocket",
"subprotocols", "subprotocols",
"unquote",
"static",
], ],
) )
FutureListener = namedtuple("FutureListener", ["listener", "event"]) FutureListener = namedtuple("FutureListener", ["listener", "event"])

View File

@ -1,7 +1,7 @@
from functools import lru_cache from functools import lru_cache
from typing import FrozenSet, Iterable, List, Optional, Union from typing import FrozenSet, Iterable, List, Optional, Union
from sanic_routing import BaseRouter, route from sanic_routing import BaseRouter
from sanic_routing.exceptions import NoMethod from sanic_routing.exceptions import NoMethod
from sanic_routing.exceptions import NotFound as RoutingNotFound from sanic_routing.exceptions import NotFound as RoutingNotFound
from sanic_routing.route import Route from sanic_routing.route import Route
@ -37,7 +37,7 @@ class Router(BaseRouter):
route, handler, params = self.resolve( route, handler, params = self.resolve(
path=request.path, path=request.path,
method=request.method, method=request.method,
extra={"host": request.headers.get("host")} extra={"host": request.headers.get("host")},
) )
except RoutingNotFound as e: except RoutingNotFound as e:
raise NotFound("Requested URL {} not found".format(e.path)) raise NotFound("Requested URL {} not found".format(e.path))
@ -75,6 +75,8 @@ class Router(BaseRouter):
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,
unquote: bool = False,
static: bool = False,
) -> Union[Route, List[Route]]: ) -> Union[Route, List[Route]]:
""" """
Add a handler to the router Add a handler to the router
@ -118,6 +120,7 @@ class Router(BaseRouter):
methods=methods, methods=methods,
name=name, name=name,
strict=strict_slashes, strict=strict_slashes,
unquote=unquote,
) )
if isinstance(host, str): if isinstance(host, str):
@ -134,6 +137,8 @@ class Router(BaseRouter):
route = super().add(**params) route = super().add(**params)
route.ctx.ignore_body = ignore_body route.ctx.ignore_body = ignore_body
route.ctx.stream = stream route.ctx.stream = stream
route.ctx.hosts = hosts
route.ctx.static = static
routes.append(route) routes.append(route)
@ -168,15 +173,19 @@ class Router(BaseRouter):
:return: tuple containing (uri, Route) :return: tuple containing (uri, Route)
""" """
if not view_name: if not view_name:
return None, None return None
if view_name == "static" or view_name.endswith(".static"): name = self.ctx.app._generate_name(view_name)
looking_for = f"_static_{name}" route = self.name_index.get(name)
route = self.name_index.get(looking_for)
else:
route = self.name_index.get(view_name)
if not route: if not route:
return None, None return None
return route.path, route return route
@property
def routes_all(self):
return {
**self.static_routes,
**self.dynamic_routes,
}

View File

@ -6,6 +6,8 @@ from re import sub
from time import gmtime, strftime from time import gmtime, strftime
from urllib.parse import unquote from urllib.parse import unquote
from sanic_routing.patterns import REGEX_TYPES
from sanic.compat import stat_async from sanic.compat import stat_async
from sanic.exceptions import ( from sanic.exceptions import (
ContentRangeError, ContentRangeError,
@ -157,11 +159,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:path>"
# 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 +176,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

View File

@ -304,24 +304,18 @@ async def test_cookie_customization(app):
_, response = await app.asgi_client.get("/cookie") _, response = await app.asgi_client.get("/cookie")
CookieDef = namedtuple("CookieDef", ("value", "httponly")) CookieDef = namedtuple("CookieDef", ("value", "httponly"))
Cookie = namedtuple("Cookie", ("domain", "path", "value", "httponly"))
cookie_map = { cookie_map = {
"test": CookieDef("Cookie1", True), "test": CookieDef("Cookie1", True),
"c2": CookieDef("Cookie2", False), "c2": CookieDef("Cookie2", False),
} }
cookies = {
c.name: Cookie(c.domain, c.path, c.value, "HttpOnly" in c._rest.keys())
for c in response.cookies.jar
}
for name, definition in cookie_map.items(): for name, definition in cookie_map.items():
cookie = cookies.get(name) cookie = response.cookies.get(name)
assert cookie assert cookie
assert cookie.value == definition.value assert cookie.value == definition.value
assert cookie.domain == "mockserver.local" assert cookie.get("domain") == "mockserver.local"
assert cookie.path == "/" assert cookie.get("path") == "/"
assert cookie.httponly == definition.httponly assert cookie.get("httponly", False) == definition.httponly
@pytest.mark.asyncio @pytest.mark.asyncio

View File

@ -197,7 +197,12 @@ def test_several_bp_with_url_prefix(app):
def test_bp_with_host(app): def test_bp_with_host(app):
bp = Blueprint("test_bp_host", url_prefix="/test1", host="example.com") bp = Blueprint(
"test_bp_host",
url_prefix="/test1",
host="example.com",
strict_slashes=True,
)
@bp.route("/") @bp.route("/")
def handler1(request): def handler1(request):
@ -209,18 +214,29 @@ def test_bp_with_host(app):
app.blueprint(bp) app.blueprint(bp)
headers = {"Host": "example.com"} headers = {"Host": "example.com"}
app.router.finalize()
request, response = app.test_client.get("/test1/", headers=headers) request, response = app.test_client.get("/test1/", headers=headers)
assert response.body == b"Hello" assert response.body == b"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)
print(app.router.find_route_src)
assert response.body == b"Hello subdomain!" assert response.body == b"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):
@ -449,6 +465,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 = []
@ -723,7 +740,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()
@ -738,9 +756,6 @@ def test_static_blueprint_name(app: Sanic, static_file_directory, file_name):
) )
app.blueprint(bp) app.blueprint(bp)
print(app.router.name_index)
print(app.router.static_routes)
print(app.router.dynamic_routes)
uri = app.url_for("static", name="static.testing") uri = app.url_for("static", name="static.testing")
assert uri == "/static/test.file" assert uri == "/static/test.file"
@ -841,18 +856,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")
app.router.finalized = False
bp = Blueprint("bp")
@bp.get("/one", strict_slashes=False) @bp.get("/one", strict_slashes=False)
def one(request): def one(request):
@ -862,7 +878,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
@ -870,19 +894,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

File diff suppressed because it is too large Load Diff

View File

@ -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")

View File

@ -112,22 +112,21 @@ def test_fails_if_endpoint_not_found(app):
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 +136,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 +166,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 +197,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 +213,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])
@ -273,11 +265,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 +282,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 +290,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"

View File

@ -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

View File

@ -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

View File

@ -1,3 +1,7 @@
import pytest
from sanic_routing.exceptions import RouteExists
from sanic.response import text from sanic.response import text
@ -38,13 +42,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"