Compare commits
	
		
			4 Commits
		
	
	
		
			fix-2388-s
			...
			flaky-test
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | a61c5ff55b | ||
|   | 7523e87937 | ||
|   | d4fb44e986 | ||
|   | 68b654d981 | 
							
								
								
									
										1
									
								
								.github/workflows/pr-python310.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/pr-python310.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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: | ||||
|   | ||||
| @@ -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. | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
							
								
								
									
										13
									
								
								sanic/app.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								sanic/app.py
									
									
									
									
									
								
							| @@ -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 | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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( | ||||
|   | ||||
							
								
								
									
										22
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								setup.py
									
									
									
									
									
								
							| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user