Compare commits

..

4 Commits

Author SHA1 Message Date
Adam Hopkins
a61c5ff55b Get better output on test failures 2022-02-27 13:29:16 +02:00
Arie Bovenberg
7523e87937 remove overlapping slots from app.Sanic, fix broken slots inherit of HTTPResponse (#2387) 2022-02-24 17:45:23 +02:00
Bluenix
d4fb44e986 Document middleware on_request and on_response (#2398) 2022-02-13 21:08:08 +02:00
Ryu juheon
68b654d981 fix(tasks): newly assigned `None` in registry (#2381) 2022-02-08 08:33:09 +02:00
13 changed files with 123 additions and 99 deletions

View File

@@ -8,7 +8,6 @@ on:
jobs:
testPy310:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:

View File

@@ -140,6 +140,7 @@ To maintain the code consistency, Sanic uses following tools.
#. `isort <https://github.com/timothycrosley/isort>`_
#. `black <https://github.com/python/black>`_
#. `flake8 <https://github.com/PyCQA/flake8>`_
#. `slotscheck <https://github.com/ariebovenberg/slotscheck>`_
isort
*****
@@ -167,7 +168,13 @@ flake8
#. pycodestyle
#. Ned Batchelder's McCabe script
``isort``\ , ``black`` and ``flake8`` checks are performed during ``tox`` lint checks.
slotscheck
**********
``slotscheck`` ensures that there are no problems with ``__slots__``
(e.g. overlaps, or missing slots in base classes).
``isort``\ , ``black``\ , ``flake8`` and ``slotscheck`` checks are performed during ``tox`` lint checks.
The **easiest** way to make your code conform is to run the following before committing.

View File

@@ -6,5 +6,5 @@ data = ""
for i in range(1, 250000):
data += str(i)
r = requests.post("http://0.0.0.0:8000/stream", data=data)
r = requests.post('http://0.0.0.0:8000/stream', data=data)
print(r.text)

View File

@@ -44,11 +44,8 @@ from typing import (
from urllib.parse import urlencode, urlunparse
from warnings import filterwarnings
from sanic_routing.exceptions import ( # type: ignore
FinalizationError,
NotFound,
)
from sanic_routing.route import Route # type: ignore
from sanic_routing.exceptions import FinalizationError, NotFound
from sanic_routing.route import Route
from sanic.application.ext import setup_ext
from sanic.application.state import ApplicationState, Mode, ServerStage
@@ -142,7 +139,6 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
"error_handler",
"go_fast",
"listeners",
"name",
"named_request_middleware",
"named_response_middleware",
"request_class",
@@ -1268,10 +1264,9 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
...
def purge_tasks(self):
for task in self.tasks:
for key, task in self._task_registry.items():
if task.done() or task.cancelled():
name = task.get_name()
self._task_registry[name] = None
self._task_registry[key] = None
self._task_registry = {
k: v for k, v in self._task_registry.items() if v is not None

View File

@@ -16,9 +16,9 @@ class MiddlewareMixin(metaclass=SanicMeta):
self, middleware_or_request, attach_to="request", apply=True
):
"""
Decorate and register middleware to be called before a request.
Can either be called as *@app.middleware* or
*@app.middleware('request')*
Decorate and register middleware to be called before a request
is handled or after a response is created. Can either be called as
*@app.middleware* or *@app.middleware('request')*.
`See user guide re: middleware
<https://sanicframework.org/guide/basics/middleware.html>`__
@@ -47,12 +47,25 @@ class MiddlewareMixin(metaclass=SanicMeta):
)
def on_request(self, middleware=None):
"""Register a middleware to be called before a request is handled.
This is the same as *@app.middleware('request')*.
:param: middleware: A callable that takes in request.
"""
if callable(middleware):
return self.middleware(middleware, "request")
else:
return partial(self.middleware, attach_to="request")
def on_response(self, middleware=None):
"""Register a middleware to be called after a response is created.
This is the same as *@app.middleware('response')*.
:param: middleware:
A callable that takes in a request and its response.
"""
if callable(middleware):
return self.middleware(middleware, "response")
else:

View File

@@ -50,6 +50,16 @@ class BaseHTTPResponse:
The base class for all HTTP Responses
"""
__slots__ = (
"asgi",
"body",
"content_type",
"stream",
"status",
"headers",
"_cookies",
)
_dumps = json_dumps
def __init__(self):
@@ -156,7 +166,7 @@ class HTTPResponse(BaseHTTPResponse):
:type content_type: Optional[str]
"""
__slots__ = ("body", "status", "content_type", "headers", "_cookies")
__slots__ = ()
def __init__(
self,

View File

@@ -1,26 +1,12 @@
import asyncio
from distutils.util import strtobool
from os import getenv
from sanic.compat import OS_IS_WINDOWS
from sanic.log import error_logger
def strtobool(query: str) -> bool:
"""
reimplement strtobool per PEP 632 and python 3.12 deprecation
True values are y, yes, t, true, on and 1; false values are n, no, f,
false, off and 0. Raises ValueError if val is anything else.
"""
if query.lower() in ["y", "yes", "t", "true", "on", "1"]:
return True
elif query.lower() in ["n", "no", "f", "false", "off", "0"]:
return False
else:
raise ValueError(f"String value {query} cannot be converted to bool")
def try_use_uvloop() -> None:
"""
Use uvloop instead of the default asyncio loop.

View File

@@ -11,18 +11,35 @@ from sanic.helpers import import_string
def str_to_bool(val: str) -> bool:
"""
reimplement strtobool per PEP 632 and python 3.12 deprecation
"""Takes string and tries to turn it into bool as human would do.
True values are y, yes, t, true, on and 1; false values are n, no, f,
false, off and 0. Raises ValueError if val is anything else.
"""
if val.lower() in ["y", "yes", "t", "true", "on", "1"]:
If val is in case insensitive (
"y", "yes", "yep", "yup", "t",
"true", "on", "enable", "enabled", "1"
) returns True.
If val is in case insensitive (
"n", "no", "f", "false", "off", "disable", "disabled", "0"
) returns False.
Else Raise ValueError."""
val = val.lower()
if val in {
"y",
"yes",
"yep",
"yup",
"t",
"true",
"on",
"enable",
"enabled",
"1",
}:
return True
elif val.lower() in ["n", "no", "f", "false", "off", "0"]:
elif val in {"n", "no", "f", "false", "off", "disable", "disabled", "0"}:
return False
else:
raise ValueError(f"String value {val} cannot be converted to bool")
raise ValueError(f"Invalid truth value {val}")
def load_module_from_file_location(

View File

@@ -6,6 +6,8 @@ import os
import re
import sys
from distutils.util import strtobool
from setuptools import find_packages, setup
from setuptools.command.test import test as TestCommand
@@ -59,7 +61,7 @@ setup_kwargs = {
"Build fast. Run fast."
),
"long_description": long_description,
"packages": find_packages(include=[]),
"packages": find_packages(),
"package_data": {"sanic": ["py.typed"]},
"platforms": "any",
"python_requires": ">=3.7",
@@ -110,6 +112,7 @@ tests_require = [
"docutils",
"pygments",
"uvicorn<0.15.0",
"slotscheck>=0.8.0,<1",
types_ujson,
]
@@ -130,23 +133,6 @@ dev_require = tests_require + [
all_require = list(set(dev_require + docs_require))
# trying to self-refernce this from within sanic prior to install is
# problematic
def strtobool(val: str) -> bool:
"""
reimplement strtobool per PEP 632 and python 3.12 deprecation
True values are y, yes, t, true, on and 1; false values are n, no, f,
false, off and 0. Raises ValueError if val is anything else.
"""
if val.lower() in ["y", "yes", "t", "true", "on", "1"]:
return True
elif val.lower() in ["n", "no", "f", "false", "off", "0"]:
return False
else:
raise ValueError(f"String value {val} cannot be converted to bool")
if strtobool(os.environ.get("SANIC_NO_UJSON", "no")):
print("Installing without uJSON")
requirements.remove(ujson)

View File

@@ -5,6 +5,7 @@ from pathlib import Path
import pytest
from pyparsing import line
from sanic_routing import __version__ as __routing_version__
from sanic import __version__
@@ -52,8 +53,10 @@ def test_server_run(appname):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
firstline = lines[starting_line(lines) + 1]
error_message = f"Lines found: {lines}\nErr output: {err}"
assert exitcode != 1
assert lines, error_message
assert firstline == b"Goin' Fast @ http://127.0.0.1:8000"
@@ -80,6 +83,9 @@ def test_tls_options(cmd):
out, err, exitcode = capture(command)
assert exitcode != 1
lines = out.split(b"\n")
error_message = f"Lines found: {lines}\nErr output: {err}"
assert lines, error_message
firstline = lines[starting_line(lines) + 1]
assert firstline == b"Goin' Fast @ https://127.0.0.1:9999"
@@ -102,7 +108,9 @@ def test_tls_wrong_options(cmd):
assert exitcode == 1
assert not out
lines = err.decode().split("\n")
error_message = f"Lines found: {lines}\nErr output: {err}"
assert lines, error_message
errmsg = lines[6]
assert errmsg == "TLS certificates must be specified by either of:"
@@ -119,9 +127,11 @@ def test_host_port_localhost(cmd):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
expected = b"Goin' Fast @ http://localhost:9999"
error_message = f"Lines found: {lines}\nErr output: {err}"
assert exitcode != 1
assert expected in lines, f"Lines found: {lines}\nErr output: {err}"
assert lines, error_message
assert expected in lines, error_message
@pytest.mark.parametrize(
@@ -136,9 +146,11 @@ def test_host_port_ipv4(cmd):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
expected = b"Goin' Fast @ http://127.0.0.127:9999"
error_message = f"Lines found: {lines}\nErr output: {err}"
assert exitcode != 1
assert expected in lines, f"Lines found: {lines}\nErr output: {err}"
assert lines, error_message
assert expected in lines, error_message
@pytest.mark.parametrize(
@@ -153,9 +165,11 @@ def test_host_port_ipv6_any(cmd):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
expected = b"Goin' Fast @ http://[::]:9999"
error_message = f"Lines found: {lines}\nErr output: {err}"
assert exitcode != 1
assert expected in lines, f"Lines found: {lines}\nErr output: {err}"
assert lines, error_message
assert expected in lines, error_message
@pytest.mark.parametrize(
@@ -170,9 +184,11 @@ def test_host_port_ipv6_loopback(cmd):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
expected = b"Goin' Fast @ http://[::1]:9999"
error_message = f"Lines found: {lines}\nErr output: {err}"
assert exitcode != 1
assert expected in lines, f"Lines found: {lines}\nErr output: {err}"
assert lines, error_message
assert expected in lines, error_message
@pytest.mark.parametrize(
@@ -206,12 +222,12 @@ def test_debug(cmd):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
info = read_app_info(lines)
error_message = f"Lines found: {lines}\nErr output: {err}"
assert info["debug"] is True, f"Lines found: {lines}\nErr output: {err}"
assert (
info["auto_reload"] is False
), f"Lines found: {lines}\nErr output: {err}"
assert "dev" not in info, f"Lines found: {lines}\nErr output: {err}"
assert info, error_message
assert info["debug"] is True, error_message
assert info["auto_reload"] is False, error_message
assert "dev" not in info, error_message
@pytest.mark.parametrize("cmd", ("--dev", "-d"))
@@ -220,11 +236,11 @@ def test_dev(cmd):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
info = read_app_info(lines)
error_message = f"Lines found: {lines}\nErr output: {err}"
assert info["debug"] is True, f"Lines found: {lines}\nErr output: {err}"
assert (
info["auto_reload"] is True
), f"Lines found: {lines}\nErr output: {err}"
assert info, error_message
assert info["debug"] is True, error_message
assert info["auto_reload"] is True, error_message
@pytest.mark.parametrize("cmd", ("--auto-reload", "-r"))
@@ -233,12 +249,12 @@ def test_auto_reload(cmd):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
info = read_app_info(lines)
error_message = f"Lines found: {lines}\nErr output: {err}"
assert info["debug"] is False, f"Lines found: {lines}\nErr output: {err}"
assert (
info["auto_reload"] is True
), f"Lines found: {lines}\nErr output: {err}"
assert "dev" not in info, f"Lines found: {lines}\nErr output: {err}"
assert info, error_message
assert info["debug"] is False, error_message
assert info["auto_reload"] is True, error_message
assert "dev" not in info, error_message
@pytest.mark.parametrize(
@@ -249,10 +265,10 @@ def test_access_logs(cmd, expected):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
info = read_app_info(lines)
error_message = f"Lines found: {lines}\nErr output: {err}"
assert (
info["access_log"] is expected
), f"Lines found: {lines}\nErr output: {err}"
assert info, error_message
assert info["access_log"] is expected, error_message
@pytest.mark.parametrize("cmd", ("--version", "-v"))
@@ -276,7 +292,7 @@ def test_noisy_exceptions(cmd, expected):
out, err, exitcode = capture(command)
lines = out.split(b"\n")
info = read_app_info(lines)
error_message = f"Lines found: {lines}\nErr output: {err}"
assert (
info["noisy_exceptions"] is expected
), f"Lines found: {lines}\nErr output: {err}"
assert info, error_message
assert info["noisy_exceptions"] is expected, error_message

View File

@@ -80,6 +80,18 @@ async def test_purge_tasks(app: Sanic):
assert len(app._task_registry) == 0
async def test_purge_tasks_with_create_task(app: Sanic):
app.add_task(asyncio.create_task(dummy(3)), name="dummy")
await app.cancel_task("dummy")
assert len(app._task_registry) == 1
app.purge_tasks()
assert len(app._task_registry) == 0
def test_shutdown_tasks_on_app_stop():
class TestSanic(Sanic):
shutdown_tasks = Mock()

View File

@@ -6,7 +6,6 @@ import pytest
from sanic.exceptions import LoadFileException
from sanic.utils import load_module_from_file_location
from sanic.utils import str_to_bool as strtobool
@pytest.mark.parametrize(
@@ -49,20 +48,3 @@ def test_load_module_from_file_location_using_env():
module = load_module_from_file_location(location)
assert isinstance(module, ModuleType)
@pytest.mark.parametrize(
"valid,values",
(
(True, ["y", "yes", "t", "true", "on", "1", "Y", "yEs", "True"]),
(False, ["n", "no", "f", "false", "off", "0", "N", "No", "False"]),
(None, ["yyy", "foo"]),
),
)
def test_strtobool(valid, values):
for value in values:
if valid is None:
with pytest.raises(ValueError):
strtobool(value)
else:
assert strtobool(value) is valid

View File

@@ -21,6 +21,7 @@ commands =
flake8 sanic
black --config ./.black.toml --check --verbose sanic/
isort --check-only sanic --profile=black
slotscheck --verbose -m sanic
[testenv:type-checking]
commands =