From c3fa036f4b8a8b7bccc0c967fb94de809ef0e320 Mon Sep 17 00:00:00 2001 From: Andriy Ivaneyko Date: Wed, 6 Dec 2023 18:59:13 -0500 Subject: [PATCH 1/2] Support for Python 3.12 #2836 --- .github/workflows/tests.yml | 1 + sanic/__version__.py | 2 +- sanic/compat.py | 4 ++-- tests/test_websockets.py | 9 +++------ tox.ini | 7 ++++--- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b071e7e4e3..531ca5d149 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,6 +41,7 @@ jobs: - { python-version: "3.10", tox-env: py310, max-attempts: 3 } - { python-version: "3.10", tox-env: py310-no-ext, max-attempts: 3 } - { python-version: "3.11", tox-env: py311, max-attempts: 3 } + - { python-version: "3.12", tox-env: py312, max-attempts: 3 } - { python-version: "3.11", tox-env: py311-no-ext, max-attempts: 3 } - { python-version: "3.8", tox-env: py38-no-ext, platform: windows-latest, ignore-errors: true } - { python-version: "3.9", tox-env: py39-no-ext, platform: windows-latest, ignore-errors: true } diff --git a/sanic/__version__.py b/sanic/__version__.py index 1cea76e950..5e62ad8939 100644 --- a/sanic/__version__.py +++ b/sanic/__version__.py @@ -1 +1 @@ -__version__ = "23.12.0" +__version__ = "23.12.1" diff --git a/sanic/compat.py b/sanic/compat.py index 62a5df9536..d7695a3136 100644 --- a/sanic/compat.py +++ b/sanic/compat.py @@ -126,11 +126,11 @@ def __getattr__(self, key: str) -> str: if key.startswith("_"): return self.__getattribute__(key) key = key.rstrip("_").replace("_", "-") - return ",".join(self.getall(key, default=[])) + return ",".join(self.getall(key, [])) def get_all(self, key: str): """Convenience method mapped to ``getall()``.""" - return self.getall(key, default=[]) + return self.getall(key, []) use_trio = sys.argv[0].endswith("hypercorn") and "trio" in sys.argv diff --git a/tests/test_websockets.py b/tests/test_websockets.py index dd8413b981..5809cfc0bb 100644 --- a/tests/test_websockets.py +++ b/tests/test_websockets.py @@ -5,7 +5,7 @@ import pytest -from websockets.frames import CTRL_OPCODES, DATA_OPCODES, Frame +from websockets.frames import CTRL_OPCODES, DATA_OPCODES, Frame, OP_TEXT from sanic.exceptions import ServerError from sanic.server.websockets.frame import WebsocketFrameAssembler @@ -210,17 +210,14 @@ async def test_ws_frame_put_message_complete(opcode): @pytest.mark.asyncio @pytest.mark.parametrize("opcode", DATA_OPCODES) async def test_ws_frame_put_message_into_queue(opcode): + foo = 'foo' if (opcode == OP_TEXT) else b"foo" assembler = WebsocketFrameAssembler(Mock()) assembler.chunks_queue = AsyncMock(spec=Queue) assembler.message_fetched = AsyncMock() assembler.message_fetched.is_set = Mock(return_value=False) - await assembler.put(Frame(opcode, b"foo")) - assembler.chunks_queue.put.has_calls( - call(b"foo"), - call(None), - ) + assert assembler.chunks_queue.put.call_args_list == [call(foo), call(None)] @pytest.mark.asyncio diff --git a/tox.ini b/tox.ini index 43ba40dd7e..487b4aeef8 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,15 @@ [tox] -envlist = py38, py39, py310, py311, pyNightly, pypy310, {py38,py39,py310,py311,pyNightly,pypy310}-no-ext, lint, check, security, docs, type-checking +envlist = py38, py39, py310, py311, py312, pyNightly, pypy310, {py38,py39,py310,py311,py312,pyNightly,pypy310}-no-ext, lint, check, security, docs, type-checking [testenv] usedevelop = true setenv = - {py38,py39,py310,py311,pyNightly}-no-ext: SANIC_NO_UJSON=1 - {py38,py39,py310,py311,pyNightly}-no-ext: SANIC_NO_UVLOOP=1 + {py38,py39,py310,py311,py312,pyNightly}-no-ext: SANIC_NO_UJSON=1 + {py38,py39,py310,py311,py312,pyNightly}-no-ext: SANIC_NO_UVLOOP=1 extras = test, http3 deps = httpx>=0.23 + setuptools allowlist_externals = pytest coverage From 21f8ac24956f11f9f5b70cf9e92197172fed4c71 Mon Sep 17 00:00:00 2001 From: Andriy Ivaneyko Date: Mon, 18 Dec 2023 19:44:07 -0500 Subject: [PATCH 2/2] Extended test suite to meet required coverage. --- tests/test_app.py | 12 +++++++++++ tests/test_blueprint_group.py | 10 +++++++++ tests/test_blueprints.py | 40 +++++++++++++++++++++++++++++++++++ tests/test_websockets.py | 4 ++-- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/tests/test_app.py b/tests/test_app.py index aefec3e62e..bc31ac96b3 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -657,3 +657,15 @@ def test_stop_trigger_terminate(app: Sanic): app.stop(unregister=False) app.multiplexer.terminate.assert_called_once() + + +def test_refresh_pass_passthru_data_to_new_instance(app: Sanic): + # arrange + passthru = { + '_inspector': 2, + 'config': {'TOUCHUP': 23} + } + app = app.refresh(passthru) + + assert app.inspector == 2 + assert app.config.TOUCHUP == 23 diff --git a/tests/test_blueprint_group.py b/tests/test_blueprint_group.py index f36d5ee932..64b666d002 100644 --- a/tests/test_blueprint_group.py +++ b/tests/test_blueprint_group.py @@ -25,6 +25,16 @@ def test_bp_group_indexing(app: Sanic): with raises(expected_exception=IndexError): _ = group[3] +def test_bp_group_set_item_by_index(app: Sanic): + blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") + blueprint_2 = Blueprint("blueprint_2", url_prefix="/bp2") + + group = Blueprint.group(blueprint_1, blueprint_2) + group[0] = blueprint_2 + + assert group[0] == blueprint_2 + + def test_bp_group_with_additional_route_params(app: Sanic): blueprint_1 = Blueprint("blueprint_1", url_prefix="/bp1") diff --git a/tests/test_blueprints.py b/tests/test_blueprints.py index be49a50fb2..c5ecc13a8d 100644 --- a/tests/test_blueprints.py +++ b/tests/test_blueprints.py @@ -1112,3 +1112,43 @@ async def index(_): app.router.finalize() assert app.router.routes[0].path == "foo/" + + +def test_blueprint_copy_returns_blueprint_with_the_name_of_original_blueprint( + app: Sanic, +): + # arrange + bp = Blueprint("bp") + + # act + actual = bp.copy("new_bp_name") + + # assert + assert bp.name == actual.copied_from + + +def test_blueprint_copy_returns_blueprint_with_overwritten_properties( + app: Sanic, +): + # arrange + bp = Blueprint("bp") + to_override_attrs = expected = dict( + url_prefix="v2", + version="v2", + version_prefix="v2", + allow_route_overwrite=True, + strict_slashes=True, + ) + + # act + actual = bp.copy( + "new_bp_name", + **to_override_attrs, + ) + + # assert + assert all( + value == getattr(actual, key) + for key, value in expected.items() + if hasattr(actual, key) + ) diff --git a/tests/test_websockets.py b/tests/test_websockets.py index 5809cfc0bb..6d31ab4d7d 100644 --- a/tests/test_websockets.py +++ b/tests/test_websockets.py @@ -5,7 +5,7 @@ import pytest -from websockets.frames import CTRL_OPCODES, DATA_OPCODES, Frame, OP_TEXT +from websockets.frames import CTRL_OPCODES, DATA_OPCODES, OP_TEXT, Frame from sanic.exceptions import ServerError from sanic.server.websockets.frame import WebsocketFrameAssembler @@ -210,7 +210,7 @@ async def test_ws_frame_put_message_complete(opcode): @pytest.mark.asyncio @pytest.mark.parametrize("opcode", DATA_OPCODES) async def test_ws_frame_put_message_into_queue(opcode): - foo = 'foo' if (opcode == OP_TEXT) else b"foo" + foo = "foo" if (opcode == OP_TEXT) else b"foo" assembler = WebsocketFrameAssembler(Mock()) assembler.chunks_queue = AsyncMock(spec=Queue) assembler.message_fetched = AsyncMock()