From fbba897d70ac8aea00f03843fabdad3224666a04 Mon Sep 17 00:00:00 2001 From: Federico Caselli Date: Wed, 21 Aug 2024 00:17:13 +0200 Subject: [PATCH 1/3] docs: note that type checking was introduced (#2287) indicate that some limitation apply --- docs/changes/4.0.0.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/changes/4.0.0.rst b/docs/changes/4.0.0.rst index 8c769d230..5df54f447 100644 --- a/docs/changes/4.0.0.rst +++ b/docs/changes/4.0.0.rst @@ -24,6 +24,20 @@ Changes to Supported Platforms later 4.x release, especially if we are faced with incompatible ecosystem changes in typing, Cython, etc. +Typing support +-------------- + +Type checking support was introduced in version 4.0. While most of the library is +now typed, further type annotations may be added throughout the 4.x release cycle. +To improve them, we may introduce changes to the typing that do not affect +runtime behavior, but may surface new or different errors with type checkers. + +.. note:: + + All type aliases in falcon are considered private, and if used should be + imported inside ``if TYPE_CHECKING:`` blocks to avoid possible import errors + after an update. + .. towncrier release notes start Contributors to this Release From a65d807b3064fb20da2ceb8972bff7503a7c64f8 Mon Sep 17 00:00:00 2001 From: Vytautas Liuolia Date: Wed, 21 Aug 2024 08:07:59 +0200 Subject: [PATCH 2/3] refactor(tests): drop `pytest-asyncio` (#2288) --- requirements/cibwtest | 1 - requirements/mintest | 1 - requirements/tests | 2 -- tests/asgi/test_asgi_conductor.py | 4 ---- tests/asgi/test_asgi_helpers.py | 2 -- tests/asgi/test_asgi_servers.py | 14 ------------- tests/asgi/test_boundedstream_asgi.py | 2 -- tests/asgi/test_buffered_reader.py | 19 ++--------------- tests/asgi/test_misc.py | 3 --- tests/asgi/test_request_body_asgi.py | 1 - tests/asgi/test_scheduled_callbacks.py | 2 -- tests/asgi/test_sync.py | 1 - tests/asgi/test_testing_asgi.py | 3 --- tests/asgi/test_ws.py | 29 -------------------------- tests/conftest.py | 11 ++++++++++ 15 files changed, 13 insertions(+), 82 deletions(-) diff --git a/requirements/cibwtest b/requirements/cibwtest index 33dd318b6..5ec7dc629 100644 --- a/requirements/cibwtest +++ b/requirements/cibwtest @@ -1,5 +1,4 @@ msgpack pytest -pytest-asyncio<0.22.0 pyyaml requests diff --git a/requirements/mintest b/requirements/mintest index 65e9332a6..158929194 100644 --- a/requirements/mintest +++ b/requirements/mintest @@ -1,4 +1,3 @@ coverage>=4.1 pytest -pytest-asyncio<0.22.0 requests diff --git a/requirements/tests b/requirements/tests index dd1fa0451..5be00de33 100644 --- a/requirements/tests +++ b/requirements/tests @@ -10,8 +10,6 @@ requests testtools; python_version < '3.10' # ASGI Specific (daphne is installed on a its own tox env) -# TODO(vytas): Some ASGI tests hang with pytest-asyncio-0.23 on 3.8 & 3.9. -pytest-asyncio < 0.22.0 aiofiles httpx uvicorn >= 0.17.0 diff --git a/tests/asgi/test_asgi_conductor.py b/tests/asgi/test_asgi_conductor.py index 2981ec4bc..0a44a692e 100644 --- a/tests/asgi/test_asgi_conductor.py +++ b/tests/asgi/test_asgi_conductor.py @@ -5,7 +5,6 @@ from falcon.asgi import App -@pytest.mark.asyncio async def test_default_headers(): class Resource: async def on_get(self, req, resp): @@ -21,7 +20,6 @@ async def on_get(self, req, resp): @pytest.mark.parametrize('simulate_method', ['request', 'simulate_request']) -@pytest.mark.asyncio async def test_generic_request(simulate_method): class Resource: async def on_lock(self, req, resp): @@ -37,7 +35,6 @@ async def on_lock(self, req, resp): assert result.status_code == 422 -@pytest.mark.asyncio async def test_wsgi_not_supported(): with pytest.raises(falcon.CompatibilityError): async with testing.TestClient(falcon.App()): @@ -52,7 +49,6 @@ async def test_wsgi_not_supported(): 'method', ['get', 'head', 'post', 'put', 'options', 'patch', 'delete'] ) @pytest.mark.parametrize('use_alias', ['alias', 'simulate']) -@pytest.mark.asyncio async def test_responders(method, use_alias): class Resource: async def on_get(self, req, resp): diff --git a/tests/asgi/test_asgi_helpers.py b/tests/asgi/test_asgi_helpers.py index f5f7b1eea..d3b172ad8 100644 --- a/tests/asgi/test_asgi_helpers.py +++ b/tests/asgi/test_asgi_helpers.py @@ -1,6 +1,5 @@ import inspect -import falcon from falcon.asgi import _asgi_helpers @@ -15,7 +14,6 @@ async def _call_factory(self, scope, receive, send): __call__ = _asgi_helpers._wrap_asgi_coroutine_func(unorthodox_call) -@falcon.runs_sync async def test_intricate_app(): async def receive(): pass diff --git a/tests/asgi/test_asgi_servers.py b/tests/asgi/test_asgi_servers.py index 5fdd9acde..c8772db40 100644 --- a/tests/asgi/test_asgi_servers.py +++ b/tests/asgi/test_asgi_servers.py @@ -174,7 +174,6 @@ def test_sse_client_disconnects_early(self, server_base_url): timeout=(_asgi_test_app.SSE_TEST_MAX_DELAY_SEC / 2), ) - @pytest.mark.asyncio @pytest.mark.skipif(httpx is None, reason='httpx is required for this test') async def test_stream_chunked_request(self, server_base_url): """Regression test for https://github.com/falconry/falcon/issues/2024""" @@ -197,7 +196,6 @@ async def emitter(): websockets is None, reason='websockets is required for this test class' ) class TestWebSocket: - @pytest.mark.asyncio @pytest.mark.parametrize('explicit_close', [True, False]) @pytest.mark.parametrize('close_code', [None, 4321]) async def test_hello(self, explicit_close, close_code, server_url_events_ws): @@ -242,7 +240,6 @@ async def test_hello(self, explicit_close, close_code, server_url_events_ws): assert got_message - @pytest.mark.asyncio @pytest.mark.parametrize('explicit_close', [True, False]) @pytest.mark.parametrize('close_code', [None, 4040]) async def test_rejected(self, explicit_close, close_code, server_url_events_ws): @@ -261,7 +258,6 @@ async def test_rejected(self, explicit_close, close_code, server_url_events_ws): assert exc_info.value.status_code == 403 - @pytest.mark.asyncio async def test_missing_responder(self, server_url_events_ws): server_url_events_ws += '/404' @@ -271,7 +267,6 @@ async def test_missing_responder(self, server_url_events_ws): assert exc_info.value.status_code == 403 - @pytest.mark.asyncio @pytest.mark.parametrize( 'subprotocol, expected', [ @@ -290,7 +285,6 @@ async def test_select_subprotocol_known( ) as ws: assert ws.subprotocol == expected - @pytest.mark.asyncio async def test_select_subprotocol_unknown(self, server_url_events_ws): extra_headers = {'X-Subprotocol': 'xmpp'} @@ -322,7 +316,6 @@ async def test_select_subprotocol_unknown(self, server_url_events_ws): # tried to capture this output and check it in the test below, # but the usual ways of capturing stdout/stderr with pytest do # not work. - @pytest.mark.asyncio async def test_disconnecting_client_early(self, server_url_events_ws): ws = await websockets.connect( server_url_events_ws, extra_headers={'X-Close': 'True'} @@ -342,7 +335,6 @@ async def test_disconnecting_client_early(self, server_url_events_ws): # messages after the close. await asyncio.sleep(1) - @pytest.mark.asyncio async def test_send_before_accept(self, server_url_events_ws): extra_headers = {'x-accept': 'skip'} @@ -352,7 +344,6 @@ async def test_send_before_accept(self, server_url_events_ws): message = await ws.recv() assert message == 'OperationNotAllowed' - @pytest.mark.asyncio async def test_recv_before_accept(self, server_url_events_ws): extra_headers = {'x-accept': 'skip', 'x-command': 'recv'} @@ -362,7 +353,6 @@ async def test_recv_before_accept(self, server_url_events_ws): message = await ws.recv() assert message == 'OperationNotAllowed' - @pytest.mark.asyncio async def test_invalid_close_code(self, server_url_events_ws): extra_headers = {'x-close': 'True', 'x-close-code': 42} @@ -379,7 +369,6 @@ async def test_invalid_close_code(self, server_url_events_ws): elapsed = time.time() - start assert elapsed < 2 - @pytest.mark.asyncio async def test_close_code_on_unhandled_error(self, server_url_events_ws): extra_headers = {'x-raise-error': 'generic'} @@ -390,7 +379,6 @@ async def test_close_code_on_unhandled_error(self, server_url_events_ws): assert ws.close_code in {3011, 1011} - @pytest.mark.asyncio async def test_close_code_on_unhandled_http_error(self, server_url_events_ws): extra_headers = {'x-raise-error': 'http'} @@ -401,7 +389,6 @@ async def test_close_code_on_unhandled_http_error(self, server_url_events_ws): assert ws.close_code == 3400 - @pytest.mark.asyncio @pytest.mark.parametrize('mismatch', ['send', 'recv']) @pytest.mark.parametrize('mismatch_type', ['text', 'data']) async def test_type_mismatch(self, mismatch, mismatch_type, server_url_events_ws): @@ -423,7 +410,6 @@ async def test_type_mismatch(self, mismatch, mismatch_type, server_url_events_ws assert ws.close_code in {3011, 1011} - @pytest.mark.asyncio async def test_passing_path_params(self, server_base_url_ws): expected_feed_id = '1ee7' url = f'{server_base_url_ws}feeds/{expected_feed_id}' diff --git a/tests/asgi/test_boundedstream_asgi.py b/tests/asgi/test_boundedstream_asgi.py index acb81215d..54e16c65f 100644 --- a/tests/asgi/test_boundedstream_asgi.py +++ b/tests/asgi/test_boundedstream_asgi.py @@ -166,7 +166,6 @@ async def test_iteration(): falcon.async_to_sync(test_iteration) -@falcon.runs_sync async def test_iterate_streaming_request(): events = iter( ( @@ -274,7 +273,6 @@ async def t(): falcon.async_to_sync(t) -@falcon.runs_sync async def test_exhaust(): emitter = testing.ASGIRequestEventEmitter(b'123456798' * 1024) stream = asgi.BoundedStream(emitter) diff --git a/tests/asgi/test_buffered_reader.py b/tests/asgi/test_buffered_reader.py index 01cab8b1c..27fbb2703 100644 --- a/tests/asgi/test_buffered_reader.py +++ b/tests/asgi/test_buffered_reader.py @@ -108,7 +108,6 @@ def test_basic_aiter(reader1): ] -@falcon.runs_sync async def test_aiter_from_buffer(reader1): assert await reader1.read(4) == b'Hell' @@ -148,27 +147,23 @@ def test_delimit(reader1, delimiter, expected): assert async_take(delimited) == expected -@falcon.runs_sync async def test_exhaust(reader1): await reader1.exhaust() assert await reader1.peek() == b'' @pytest.mark.parametrize('size', [1, 2, 3, 5, 7, 8]) -@falcon.runs_sync async def test_peek(reader1, size): assert await reader1.peek(size) == b'Hello, World'[:size] assert reader1.tell() == 0 -@falcon.runs_sync async def test_peek_at_eof(): source = chop_data(b'Hello!') stream = reader.BufferedReader(source) assert await stream.peek(16) == b'Hello!' -@falcon.runs_sync async def test_pipe(reader1): sink = AsyncSink() await reader1.pipe(sink) @@ -177,7 +172,6 @@ async def test_pipe(reader1): assert reader1.tell() == len(sink.accumulated) -@falcon.runs_sync async def test_pipe_until_delimiter_not_found(reader1): sink = AsyncSink() await reader1.pipe_until(b'404', sink) @@ -202,7 +196,6 @@ async def test_pipe_until_delimiter_not_found(reader1): ((50, 1), [b'Hello, World!\nJust testing some iterator goodness.', b'\n']), ], ) -@falcon.runs_sync async def test_read(reader1, sizes, expected): results = [] for size in sizes: @@ -213,7 +206,6 @@ async def test_read(reader1, sizes, expected): @pytest.mark.parametrize('start_size', [1, 16777216]) @pytest.mark.slow -@falcon.runs_sync async def test_varying_read_size(reader2, start_size): size = start_size result = io.BytesIO() @@ -230,7 +222,6 @@ async def test_varying_read_size(reader2, start_size): @pytest.mark.parametrize('peek', [0, 1, 8]) -@falcon.runs_sync async def test_readall(reader1, peek): if peek: await reader1.peek(peek) @@ -263,7 +254,6 @@ async def test_readall(reader1, peek): (7, b'good', -1, b'World!\nJust testing some iterator '), ], ) -@falcon.runs_sync async def test_read_until(reader1, offset, delimiter, size, expected, fork): if offset: await reader1.read(offset) @@ -274,7 +264,6 @@ async def test_read_until(reader1, offset, delimiter, size, expected, fork): assert await reader1.read_until(delimiter, size) == expected -@falcon.runs_sync async def test_read_until_with_buffer_edge_case(reader1): assert await reader1.read(12) == b'Hello, World' assert await reader1.peek(1) == b'!' @@ -292,7 +281,6 @@ def test_placeholder_methods(reader1): assert not reader1.writable() -@falcon.runs_sync async def test_iteration_started(reader1): async for chunk in reader1: with pytest.raises(OperationNotAllowed): @@ -300,7 +288,6 @@ async def test_iteration_started(reader1): pass -@falcon.runs_sync async def test_invalid_delimiter_length(reader1): with pytest.raises(ValueError): await reader1.read_until(b'') @@ -320,7 +307,6 @@ async def test_invalid_delimiter_length(reader1): ], ) @pytest.mark.slow -@falcon.runs_sync async def test_irregular_large_read_until(reader2, size1, size2): delimiter = b'--boundary1234567890--' @@ -341,7 +327,6 @@ async def test_irregular_large_read_until(reader2, size1, size2): assert chunk1 + chunk2 + remainder == expected[1337:] -@falcon.runs_sync @pytest.mark.parametrize('chunk_size', list(range(46, 63))) async def test_read_until_shared_boundary(chunk_size): source = chop_data( @@ -356,7 +341,8 @@ async def test_read_until_shared_boundary(chunk_size): # NOTE(vytas): This is woefully unoptimized, and this test highlights that. # Work in progress. -@falcon.runs_sync + + async def test_small_reads(reader3): ops = 0 read = 0 @@ -379,7 +365,6 @@ async def test_small_reads(reader3): @pytest.mark.slow -@falcon.runs_sync async def test_small_reads_with_delimiter(reader3): ops = 0 read = 0 diff --git a/tests/asgi/test_misc.py b/tests/asgi/test_misc.py index 1a609db3e..1a66c6955 100644 --- a/tests/asgi/test_misc.py +++ b/tests/asgi/test_misc.py @@ -9,21 +9,18 @@ from falcon.http_status import HTTPStatus -@pytest.mark.asyncio async def test_http_status_not_impl(): app = App() with pytest.raises(NotImplementedError): await app._http_status_handler(MagicMock(), None, HTTPStatus(200), {}, None) -@pytest.mark.asyncio async def test_http_error_not_impl(): app = App() with pytest.raises(NotImplementedError): await app._http_error_handler(MagicMock(), None, HTTPError(400), {}, None) -@pytest.mark.asyncio async def test_python_error_not_impl(): app = App() with pytest.raises(NotImplementedError): diff --git a/tests/asgi/test_request_body_asgi.py b/tests/asgi/test_request_body_asgi.py index fed5267b9..1a3d9b445 100644 --- a/tests/asgi/test_request_body_asgi.py +++ b/tests/asgi/test_request_body_asgi.py @@ -63,7 +63,6 @@ def test_tiny_body_overflow(self, client, resource): (8192, 50), ], ) - @pytest.mark.asyncio async def test_content_length_smaller_than_body(self, body_length, content_length): body_in = os.urandom(body_length) diff --git a/tests/asgi/test_scheduled_callbacks.py b/tests/asgi/test_scheduled_callbacks.py index 36d4f4e5b..c37ff3809 100644 --- a/tests/asgi/test_scheduled_callbacks.py +++ b/tests/asgi/test_scheduled_callbacks.py @@ -4,7 +4,6 @@ import pytest -from falcon import runs_sync from falcon import testing from falcon.asgi import App @@ -138,7 +137,6 @@ def callback_app(simple_resource): ('GET', '/stream', 'One\nTwo\nThree\n'), ], ) -@runs_sync async def test_callback(callback_app, simple_resource, method, uri, expected): async with testing.ASGIConductor(callback_app) as conductor: resp = await conductor.simulate_request(method, uri) diff --git a/tests/asgi/test_sync.py b/tests/asgi/test_sync.py index 6baab8c75..653beaad4 100644 --- a/tests/asgi/test_sync.py +++ b/tests/asgi/test_sync.py @@ -110,7 +110,6 @@ def callme_shirley(a=42, b=None): assert val[1] is None or (0 <= val[1] < 1000) -@pytest.mark.asyncio async def test_sync_asyncio_aliases(): async def dummy_async_func(): pass diff --git a/tests/asgi/test_testing_asgi.py b/tests/asgi/test_testing_asgi.py index 915fec4de..8ec041361 100644 --- a/tests/asgi/test_testing_asgi.py +++ b/tests/asgi/test_testing_asgi.py @@ -8,7 +8,6 @@ from . import _asgi_test_app -@pytest.mark.asyncio @pytest.mark.slow async def test_asgi_request_event_emitter_hang(): # NOTE(kgriffs): This tests the ASGI server behavior that @@ -36,7 +35,6 @@ async def test_asgi_request_event_emitter_hang(): assert (elapsed + 0.1) > expected_elapsed_min -@pytest.mark.asyncio async def test_ignore_extra_asgi_events(): collect = testing.ASGIResponseEventCollector() @@ -49,7 +47,6 @@ async def test_ignore_extra_asgi_events(): assert len(collect.events) == 2 -@pytest.mark.asyncio async def test_invalid_asgi_events(): collect = testing.ASGIResponseEventCollector() diff --git a/tests/asgi/test_ws.py b/tests/asgi/test_ws.py index 6fb7b7667..87ea0c588 100644 --- a/tests/asgi/test_ws.py +++ b/tests/asgi/test_ws.py @@ -55,7 +55,6 @@ def conductor(): return testing.ASGIConductor(app) -@pytest.mark.asyncio @pytest.mark.parametrize('path', ['/ws/yes', '/ws/no']) async def test_ws_not_accepted(path, conductor): class SomeResource: @@ -113,7 +112,6 @@ async def on_websocket(self, req, ws, explicit): assert resource.caught_operation_not_allowed -@pytest.mark.asyncio @pytest.mark.slow async def test_echo(): # noqa: C901 consumer_sleep = 0.01 @@ -232,7 +230,6 @@ async def process_request_ws(self, req, ws): assert resource.caught_operation_not_allowed -@pytest.mark.asyncio async def test_path_not_found(conductor): async with conductor as c: with pytest.raises(falcon.WebSocketDisconnected) as exc_info: @@ -242,7 +239,6 @@ async def test_path_not_found(conductor): assert exc_info.value.code == CloseCode.NOT_FOUND -@pytest.mark.asyncio @pytest.mark.parametrize('clear_error_handlers', [True, False]) async def test_responder_raises_unhandled_error(clear_error_handlers, conductor): class SomeResource: @@ -302,7 +298,6 @@ async def on_websocket(self, req, ws, generic_error): assert exc_info.value.code == 3422 -@pytest.mark.asyncio @pytest.mark.parametrize('direction', ['send', 'receive']) @pytest.mark.parametrize('explicit_close_client', [True, False]) @pytest.mark.parametrize('explicit_close_server', [True, False]) @@ -410,7 +405,6 @@ async def on_websocket(self, req, ws): assert resource.ws_ready is False -@pytest.mark.asyncio @pytest.mark.parametrize('custom_text', [True, False]) @pytest.mark.parametrize('custom_data', [True, False]) @pytest.mark.skipif(msgpack is None, reason='msgpack is required for this test') @@ -525,7 +519,6 @@ def deserialize(self, payload: bytes) -> object: assert doc == doc_expected -@pytest.mark.asyncio @pytest.mark.parametrize('sample_data', [b'123', b'', b'\xe1\x9a\xa0\xe1', b'\0']) async def test_send_receive_data(sample_data, conductor): class Resource: @@ -567,7 +560,6 @@ async def on_websocket(self, req, ws): assert resource.error_count == 4 -@pytest.mark.asyncio @pytest.mark.parametrize( 'subprotocols', [ @@ -605,7 +597,6 @@ async def on_websocket(self, req, ws): resource.test_complete.set() -@pytest.mark.asyncio @pytest.mark.parametrize( 'headers', [ @@ -654,7 +645,6 @@ async def on_websocket(self, req, ws): resource.test_complete.set() -@pytest.mark.asyncio @pytest.mark.parametrize( 'headers', [ @@ -688,7 +678,6 @@ async def on_websocket(self, req, ws): assert isinstance(resource.raised_error, ValueError) -@pytest.mark.asyncio async def test_accept_with_headers_not_supported(conductor): class Resource: def __init__(self): @@ -714,7 +703,6 @@ async def on_websocket(self, req, ws): assert isinstance(resource.raised_error, falcon.OperationNotAllowed) -@pytest.mark.asyncio async def test_missing_ws_handler(conductor): class Resource: async def on_get(self, req, resp): @@ -728,7 +716,6 @@ async def on_get(self, req, resp): pass -@pytest.mark.asyncio async def test_unexpected_param(conductor): class Resource: async def on_websocket(self, req, ws): @@ -742,7 +729,6 @@ async def on_websocket(self, req, ws): pass -@pytest.mark.asyncio @pytest.mark.parametrize( 'subprotocol', [ @@ -775,7 +761,6 @@ async def on_websocket(self, req, ws): pass -@pytest.mark.asyncio async def test_send_receive_wrong_type(conductor): class Resource: def __init__(self): @@ -830,7 +815,6 @@ async def on_websocket(self, req, ws): assert resource.error_count == 4 -@pytest.mark.asyncio @pytest.mark.parametrize( 'options_code', [999, 100, 0, -1, 1004, 1005, 1006, 1015, 1016, 1017, 1050, 1099, 'NaN'], @@ -883,7 +867,6 @@ def process_request_ws(self, req, ws): App(middleware=mw) -@pytest.mark.asyncio @pytest.mark.parametrize('version', ['1.9', '20.5', '3.0', '3.1']) async def test_bad_spec_version(version, conductor): async with conductor as c: @@ -892,7 +875,6 @@ async def test_bad_spec_version(version, conductor): pass -@pytest.mark.asyncio @pytest.mark.parametrize('version', ['1.0', '1']) async def test_bad_http_version(version, conductor): async with conductor as c: @@ -901,7 +883,6 @@ async def test_bad_http_version(version, conductor): pass -@pytest.mark.asyncio async def test_bad_first_event(): app = App() @@ -928,7 +909,6 @@ async def _emit(): assert ws.close_code == CloseCode.SERVER_ERROR -@pytest.mark.asyncio async def test_missing_http_version(): app = App() @@ -942,7 +922,6 @@ async def test_missing_http_version(): await app(scope, ws._emit, ws._collect) -@pytest.mark.asyncio async def test_missing_spec_version(): app = App() @@ -956,7 +935,6 @@ async def test_missing_spec_version(): await app(scope, ws._emit, ws._collect) -@pytest.mark.asyncio async def test_translate_webserver_error(conductor): class Resource: def __init__(self): @@ -1025,7 +1003,6 @@ def test_ws_base_not_implemented(): bh.deserialize(b'') -@pytest.mark.asyncio @pytest.mark.slow async def test_ws_context_timeout(conductor): class Resource: @@ -1040,7 +1017,6 @@ async def on_websocket(self, req, ws): pass -@pytest.mark.asyncio async def test_ws_simulator_client_require_accepted(conductor): class Resource: async def on_websocket(self, req, ws): @@ -1059,7 +1035,6 @@ async def on_websocket(self, req, ws): await ws.receive_text() -@pytest.mark.asyncio async def test_ws_simulator_collect_edge_cases(conductor): class Resource: pass @@ -1098,7 +1073,6 @@ class Resource: event = await ws._emit() -@pytest.mark.asyncio @pytest.mark.slow async def test_ws_responder_never_ready(conductor, monkeypatch): async def noop_close(obj, code=None): @@ -1139,7 +1113,6 @@ def test_msgpack_missing(): handler.deserialize(b'{}') -@pytest.mark.asyncio @pytest.mark.parametrize('status', [200, 500, 422, 400]) @pytest.mark.parametrize('thing', [falcon.HTTPStatus, falcon.HTTPError]) @pytest.mark.parametrize('accept', [True, False]) @@ -1168,7 +1141,6 @@ async def on_websocket(self, req, ws): assert err.value.code == exp_code -@pytest.mark.asyncio @pytest.mark.parametrize('status', [200, 500, 422, 400]) @pytest.mark.parametrize( 'thing', @@ -1211,7 +1183,6 @@ class FooBarError(Exception): pass -@pytest.mark.asyncio @pytest.mark.parametrize('status', [200, 500, 422, 400]) @pytest.mark.parametrize('thing', [falcon.HTTPStatus, falcon.HTTPError]) @pytest.mark.parametrize( diff --git a/tests/conftest.py b/tests/conftest.py index e26f0cefe..6d47362a2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import contextlib import importlib.util +import inspect import os import pathlib @@ -139,3 +140,13 @@ def pytest_runtest_protocol(item, nextitem): item.cls._item = item yield + + +@pytest.hookimpl(hookwrapper=True) +def pytest_runtest_call(item): + # NOTE(vytas): We automatically wrap all coroutine functions with + # falcon.runs_sync instead of the fragile pytest-asyncio package. + if isinstance(item, pytest.Function) and inspect.iscoroutinefunction(item.obj): + item.obj = falcon.runs_sync(item.obj) + + yield From edcd5f63eb2240cb1bd37cb7275485d26ad44435 Mon Sep 17 00:00:00 2001 From: Vytautas Liuolia Date: Wed, 21 Aug 2024 13:17:48 +0200 Subject: [PATCH 3/3] refactor(tests): remove `_util` import in favour of fixture (#2281) * refactor(tests): migrate some usage of _util to util fixture * style: remove unused imports * refactor(tests): move more stuff out from _util * refactor(tests): remove usage of _util.create_resp etc * refactor(tests): significantly reduce the usage of _util * refactor(tests): move has_cython to util * refactor: use util in tests_middleware.py * refactor(tests): finally remove the thing * chore(tests): remove redundant old parametrization --- tests/_util.py | 79 ------------- tests/asgi/test_cythonized_asgi.py | 10 +- tests/asgi/test_hello_asgi.py | 5 +- tests/conftest.py | 26 +++-- tests/test_after_hooks.py | 12 +- tests/test_before_hooks.py | 23 ++-- tests/test_cmd_inspect_app.py | 7 +- tests/test_cookies.py | 5 +- tests/test_cors_middleware.py | 16 ++- tests/test_custom_router.py | 26 ++--- tests/test_cython.py | 11 +- tests/test_default_router.py | 10 +- tests/test_error_handlers.py | 28 +++-- tests/test_headers.py | 13 +-- tests/test_http_custom_method_routing.py | 14 ++- tests/test_http_method_routing.py | 5 +- tests/test_httperror.py | 9 +- tests/test_httpstatus.py | 21 ++-- tests/test_media_handlers.py | 25 ++-- tests/test_media_multipart.py | 5 +- tests/test_media_urlencoded.py | 5 +- tests/test_middleware.py | 139 +++++++++++------------ tests/test_query_params.py | 14 +-- tests/test_redirects.py | 9 +- tests/test_request_access_route.py | 33 +++--- tests/test_request_attrs.py | 9 +- tests/test_request_forwarded.py | 45 ++++---- tests/test_request_media.py | 5 +- tests/test_response.py | 7 +- tests/test_response_body.py | 22 ++-- tests/test_sink_and_static.py | 5 +- tests/test_sinks.py | 10 +- tests/test_static.py | 37 +++--- tests/test_testing.py | 5 +- tests/test_uri_converters.py | 12 +- tests/test_uri_templates.py | 17 ++- tests/test_utils.py | 48 ++++---- tests/test_validators.py | 21 ++-- 38 files changed, 354 insertions(+), 439 deletions(-) delete mode 100644 tests/_util.py diff --git a/tests/_util.py b/tests/_util.py deleted file mode 100644 index 85070a550..000000000 --- a/tests/_util.py +++ /dev/null @@ -1,79 +0,0 @@ -from contextlib import contextmanager -import os - -import pytest - -import falcon -import falcon.asgi -import falcon.testing - -try: - import cython # noqa - - has_cython = True -except ImportError: - try: - import falcon.cyutil.reader # noqa - - has_cython = True - except ImportError: - has_cython = False - -__all__ = [ - 'create_app', - 'create_req', - 'create_resp', - 'to_coroutine', -] - - -def create_app(asgi, **app_kwargs): - App = falcon.asgi.App if asgi else falcon.App - app = App(**app_kwargs) - return app - - -def create_req(asgi, options=None, **environ_or_scope_kwargs): - if asgi: - req = falcon.testing.create_asgi_req(options=options, **environ_or_scope_kwargs) - - else: - req = falcon.testing.create_req(options=options, **environ_or_scope_kwargs) - - return req - - -def create_resp(asgi): - if asgi: - return falcon.asgi.Response() - - return falcon.Response() - - -def to_coroutine(callable): - async def wrapper(*args, **kwargs): - return callable(*args, **kwargs) - - return wrapper - - -@contextmanager -def disable_asgi_non_coroutine_wrapping(): - should_wrap = 'FALCON_ASGI_WRAP_NON_COROUTINES' in os.environ - if should_wrap: - del os.environ['FALCON_ASGI_WRAP_NON_COROUTINES'] - - yield - - if should_wrap: - os.environ['FALCON_ASGI_WRAP_NON_COROUTINES'] = 'Y' - - -def as_params(*values, prefix=None): - if not prefix: - prefix = '' - # NOTE(caselit): each value must be a tuple/list even when using one single argument - return [ - pytest.param(*value, id=f'{prefix}_{i}' if prefix else f'{i}') - for i, value in enumerate(values, 1) - ] diff --git a/tests/asgi/test_cythonized_asgi.py b/tests/asgi/test_cythonized_asgi.py index 744dcd952..67d7499b7 100644 --- a/tests/asgi/test_cythonized_asgi.py +++ b/tests/asgi/test_cythonized_asgi.py @@ -31,8 +31,6 @@ else: _CYTHON_FUNC_TEST_TYPES = [] -from _util import disable_asgi_non_coroutine_wrapping # NOQA - # NOTE(vytas): Cython 3.0+ now correctly marks cythonized coroutines as such, # however, the relevant protocol is only available in Python 3.10+. @@ -83,8 +81,8 @@ def test_not_cython_func(func): @pytest.mark.skipif(not pyximport, reason='Cython not installed') -def test_jsonchema_validator(client): - with disable_asgi_non_coroutine_wrapping(): +def test_jsonchema_validator(client, util): + with util.disable_asgi_non_coroutine_wrapping(): if CYTHON_COROUTINE_HINT: client.app.add_route('/', _cythonized.TestResourceWithValidationNoHint()) else: @@ -126,8 +124,8 @@ def test_scheduled_jobs_type_error(client): @pytest.mark.skipif(not pyximport, reason='Cython not installed') -def test_hooks(client): - with disable_asgi_non_coroutine_wrapping(): +def test_hooks(client, util): + with util.disable_asgi_non_coroutine_wrapping(): if CYTHON_COROUTINE_HINT: client.app.add_route('/', _cythonized.TestResourceWithHooksNoHint()) else: diff --git a/tests/asgi/test_hello_asgi.py b/tests/asgi/test_hello_asgi.py index fb19e3c61..dc1527a34 100644 --- a/tests/asgi/test_hello_asgi.py +++ b/tests/asgi/test_hello_asgi.py @@ -2,7 +2,6 @@ import os import tempfile -from _util import disable_asgi_non_coroutine_wrapping # NOQA import pytest import falcon @@ -376,8 +375,8 @@ def test_status_not_set(self, client): assert not result.content assert result.status_code == 200 - def test_coroutine_required(self, client): - with disable_asgi_non_coroutine_wrapping(): + def test_coroutine_required(self, client, util): + with util.disable_asgi_non_coroutine_wrapping(): with pytest.raises(TypeError) as exinfo: client.app.add_route('/', PartialCoroutineResource()) diff --git a/tests/conftest.py b/tests/conftest.py index 6d47362a2..a41293e3f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,6 +8,19 @@ import falcon import falcon.asgi +import falcon.testing + +try: + import cython # noqa + + has_cython = True +except ImportError: + try: + import falcon.cyutil.reader # noqa + + has_cython = True + except ImportError: + has_cython = False HERE = pathlib.Path(__file__).resolve().parent FALCON_ROOT = HERE.parent @@ -34,6 +47,8 @@ def app_kind(asgi): class _SuiteUtils: """Assorted utilities that previously resided in the _util.py module.""" + HAS_CYTHON = has_cython + @staticmethod def create_app(asgi, **app_kwargs): App = falcon.asgi.App if asgi else falcon.App @@ -75,17 +90,6 @@ def disable_asgi_non_coroutine_wrapping(): if should_wrap: os.environ['FALCON_ASGI_WRAP_NON_COROUTINES'] = 'Y' - @staticmethod - def as_params(*values, prefix=None): - if not prefix: - prefix = '' - # NOTE(caselit): each value must be a tuple/list even when using one - # single argument - return [ - pytest.param(*value, id=f'{prefix}_{i}' if prefix else f'{i}') - for i, value in enumerate(values, 1) - ] - @staticmethod def load_module(filename, parent_dir=None, suffix=None): if parent_dir: diff --git a/tests/test_after_hooks.py b/tests/test_after_hooks.py index 46bb07771..442788373 100644 --- a/tests/test_after_hooks.py +++ b/tests/test_after_hooks.py @@ -1,8 +1,6 @@ import functools import json -from _util import create_app # NOQA -from _util import create_resp # NOQA import pytest import falcon @@ -19,8 +17,8 @@ def wrapped_resource_aware(): @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) resource = WrappedRespondersResourceAsync() if asgi else WrappedRespondersResource() app.add_route('/', resource) @@ -259,8 +257,8 @@ def test_resource_with_uri_fields(client, resource): assert resource.fields == ('82074', '58927') -def test_resource_with_uri_fields_async(): - app = create_app(asgi=True) +def test_resource_with_uri_fields_async(util): + app = util.create_app(asgi=True) resource = ClassResourceWithURIFieldsAsync() app.add_route('/{field1}/{field2}', resource) @@ -275,7 +273,7 @@ async def test_direct(): resource = ClassResourceWithURIFieldsAsync() req = testing.create_asgi_req() - resp = create_resp(True) + resp = util.create_resp(True) await resource.on_get(req, resp, '1', '2') assert resource.fields == ('1', '2') diff --git a/tests/test_before_hooks.py b/tests/test_before_hooks.py index 23d5eed57..40e6ba7a6 100644 --- a/tests/test_before_hooks.py +++ b/tests/test_before_hooks.py @@ -2,9 +2,6 @@ import io import json -from _util import create_app # NOQA -from _util import create_resp # NOQA -from _util import disable_asgi_non_coroutine_wrapping # NOQA import pytest import falcon @@ -246,8 +243,8 @@ def resource(): @pytest.fixture -def client(asgi, request, resource): - app = create_app(asgi) +def client(asgi, util, request, resource): + app = util.create_app(asgi) app.add_route('/', resource) return testing.TestClient(app) @@ -337,8 +334,8 @@ def test_parser_sync(body, doc): (None, None), ], ) -def test_parser_async(body, doc): - with disable_asgi_non_coroutine_wrapping(): +def test_parser_async(body, doc, util): + with util.disable_asgi_non_coroutine_wrapping(): class WrappedRespondersBodyParserAsyncResource: @falcon.before(validate_param_async, 'limit', 100, is_async=True) @@ -350,7 +347,7 @@ async def on_get(self, req, resp, doc=None): async def on_put(self, req, resp, doc=None): self.doc = doc - app = create_app(asgi=True) + app = util.create_app(asgi=True) resource = WrappedRespondersBodyParserAsyncResource() app.add_route('/', resource) @@ -365,7 +362,7 @@ async def test_direct(): resource = WrappedRespondersBodyParserAsyncResource() req = testing.create_asgi_req() - resp = create_resp(True) + resp = util.create_resp(True) await resource.on_get(req, resp, doc) assert resource.doc == doc @@ -479,11 +476,11 @@ async def on_post_collection(self, req, resp): resp.status = falcon.HTTP_CREATED -@pytest.fixture(params=[True, False]) -def app_client(request): - items = PiggybackingCollectionAsync() if request.param else PiggybackingCollection() +@pytest.fixture() +def app_client(asgi, util): + items = PiggybackingCollectionAsync() if asgi else PiggybackingCollection() - app = create_app(asgi=request.param) + app = util.create_app(asgi) app.add_route('/items', items, suffix='collection') app.add_route('/items/{itemid:int}', items) diff --git a/tests/test_cmd_inspect_app.py b/tests/test_cmd_inspect_app.py index f2b4e895f..837849520 100644 --- a/tests/test_cmd_inspect_app.py +++ b/tests/test_cmd_inspect_app.py @@ -2,11 +2,11 @@ import io import sys -from _util import create_app # NOQA import pytest from falcon import App from falcon import inspect +import falcon.asgi from falcon.cmd import inspect_app from falcon.testing import redirected @@ -30,6 +30,11 @@ async def on_get(self, req, resp): resp.status = '200 OK' +def create_app(asgi): + app_cls = falcon.asgi.App if asgi else App + return app_cls() + + def make_app(asgi=False): app = create_app(asgi) app.add_route('/test', DummyResourceAsync() if asgi else DummyResource()) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 7f6dfc310..bc27f4da1 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -5,7 +5,6 @@ from http import cookies as http_cookies import re -from _util import create_app # NOQA import pytest import falcon @@ -107,8 +106,8 @@ def on_get(self, req, resp): @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) app.add_route('/', CookieResource()) app.add_route('/test-convert', CookieResourceMaxAgeFloatString()) app.add_route('/same-site', CookieResourceSameSite()) diff --git a/tests/test_cors_middleware.py b/tests/test_cors_middleware.py index dae9a7ee7..244d22398 100644 --- a/tests/test_cors_middleware.py +++ b/tests/test_cors_middleware.py @@ -1,5 +1,3 @@ -from _util import create_app # NOQA -from _util import disable_asgi_non_coroutine_wrapping # NOQA import pytest import falcon @@ -7,17 +5,17 @@ @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) return testing.TestClient(app) @pytest.fixture(scope='function') -def cors_client(asgi): +def cors_client(asgi, util): # NOTE(kgriffs): Disable wrapping to test that built-in middleware does # not require it (since this will be the case for non-test apps). - with disable_asgi_non_coroutine_wrapping(): - app = create_app(asgi, cors_enable=True) + with util.disable_asgi_non_coroutine_wrapping(): + app = util.create_app(asgi, cors_enable=True) return testing.TestClient(app) @@ -98,9 +96,9 @@ def test_enabled_cors_handles_preflighting_no_headers_in_req(self, cors_client): @pytest.fixture(scope='function') -def make_cors_client(asgi): +def make_cors_client(asgi, util): def make(middleware): - app = create_app(asgi, middleware=middleware) + app = util.create_app(asgi, middleware=middleware) return testing.TestClient(app) return make diff --git a/tests/test_custom_router.py b/tests/test_custom_router.py index f38c914eb..04b048d67 100644 --- a/tests/test_custom_router.py +++ b/tests/test_custom_router.py @@ -1,12 +1,10 @@ -from _util import create_app # NOQA import pytest import falcon from falcon import testing -@pytest.mark.parametrize('asgi', [True, False]) -def test_custom_router_add_route_should_be_used(asgi): +def test_custom_router_add_route_should_be_used(asgi, util): check = [] class CustomRouter: @@ -16,15 +14,14 @@ def add_route(self, uri_template, *args, **kwargs): def find(self, uri): pass - app = create_app(asgi=asgi, router=CustomRouter()) + app = util.create_app(asgi=asgi, router=CustomRouter()) app.add_route('/test', 'resource') assert len(check) == 1 assert '/test' in check -@pytest.mark.parametrize('asgi', [True, False]) -def test_custom_router_find_should_be_used(asgi): +def test_custom_router_find_should_be_used(asgi, util): if asgi: async def resource(req, resp, **kwargs): @@ -56,7 +53,7 @@ def find(self, uri, req=None): return None router = CustomRouter() - app = create_app(asgi=asgi, router=router) + app = util.create_app(asgi=asgi, router=router) client = testing.TestClient(app) response = client.simulate_request(path='/test/42') @@ -76,8 +73,7 @@ def find(self, uri, req=None): assert router.reached_backwards_compat -@pytest.mark.parametrize('asgi', [True, False]) -def test_can_pass_additional_params_to_add_route(asgi): +def test_can_pass_additional_params_to_add_route(asgi, util): check = [] class CustomRouter: @@ -89,7 +85,7 @@ def add_route(self, uri_template, resource, **kwargs): def find(self, uri): pass - app = create_app(asgi=asgi, router=CustomRouter()) + app = util.create_app(asgi=asgi, router=CustomRouter()) app.add_route('/test', 'resource', name='my-url-name') assert len(check) == 1 @@ -102,8 +98,7 @@ def find(self, uri): app.add_route('/test', 'resource', 'xarg1', 'xarg2') -@pytest.mark.parametrize('asgi', [True, False]) -def test_custom_router_takes_req_positional_argument(asgi): +def test_custom_router_takes_req_positional_argument(asgi, util): if asgi: async def responder(req, resp): @@ -120,14 +115,13 @@ def find(self, uri, req): return responder, {'GET': responder}, {}, None router = CustomRouter() - app = create_app(asgi=asgi, router=router) + app = util.create_app(asgi=asgi, router=router) client = testing.TestClient(app) response = client.simulate_request(path='/test') assert response.content == b'OK' -@pytest.mark.parametrize('asgi', [True, False]) -def test_custom_router_takes_req_keyword_argument(asgi): +def test_custom_router_takes_req_keyword_argument(asgi, util): if asgi: async def responder(req, resp): @@ -144,7 +138,7 @@ def find(self, uri, req=None): return responder, {'GET': responder}, {}, None router = CustomRouter() - app = create_app(asgi=asgi, router=router) + app = util.create_app(asgi=asgi, router=router) client = testing.TestClient(app) response = client.simulate_request(path='/test') assert response.content == b'OK' diff --git a/tests/test_cython.py b/tests/test_cython.py index ea7c92bbb..a1f6e75c6 100644 --- a/tests/test_cython.py +++ b/tests/test_cython.py @@ -1,6 +1,5 @@ import io -from _util import has_cython # NOQA import pytest import falcon @@ -8,14 +7,16 @@ class TestCythonized: - @pytest.mark.skipif(not has_cython, reason='Cython not installed') - def test_imported_from_c_modules(self): + def test_imported_from_c_modules(self, util): + if not util.HAS_CYTHON: + pytest.skip(reason='Cython not installed') + assert 'falcon/app.py' not in str(falcon.app) - def test_stream_has_private_read(self): + def test_stream_has_private_read(self, util): stream = falcon.util.BufferedReader(io.BytesIO().read, 8) - if has_cython and falcon.util.IS_64_BITS: + if util.HAS_CYTHON and falcon.util.IS_64_BITS: assert not hasattr(stream, '_read') else: assert hasattr(stream, '_read') diff --git a/tests/test_default_router.py b/tests/test_default_router.py index de9dc83e0..9f8a05405 100644 --- a/tests/test_default_router.py +++ b/tests/test_default_router.py @@ -1,14 +1,13 @@ import textwrap -from _util import create_app # NOQA import pytest from falcon import testing from falcon.routing import DefaultRouter -def client(asgi): - return testing.TestClient(create_app(asgi)) +def client(asgi, util): + return testing.TestClient(util.create_app(asgi)) @pytest.fixture @@ -249,10 +248,9 @@ def test_user_regression_special_chars(uri_template, path, expected_params): # ===================================================================== -@pytest.mark.parametrize('asgi', [True, False]) @pytest.mark.parametrize('uri_template', [{}, set(), object()]) -def test_not_str(asgi, uri_template): - app = create_app(asgi) +def test_not_str(asgi, util, uri_template): + app = util.create_app(asgi) with pytest.raises(TypeError): app.add_route(uri_template, ResourceWithId(-1)) diff --git a/tests/test_error_handlers.py b/tests/test_error_handlers.py index c6c5785ff..751323c20 100644 --- a/tests/test_error_handlers.py +++ b/tests/test_error_handlers.py @@ -1,5 +1,3 @@ -from _util import create_app # NOQA -from _util import disable_asgi_non_coroutine_wrapping # NOQA import pytest import falcon @@ -51,8 +49,8 @@ def on_delete(self, req, resp): @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) app.add_route('/', ErroredClassResource()) return testing.TestClient(app) @@ -195,7 +193,7 @@ def test_invalid_add_exception_handler_input(self, client, exceptions): with pytest.raises(TypeError): client.app.add_error_handler(exceptions, capture_error) - def test_handler_signature_shim(self): + def test_handler_signature_shim(self, util): def check_args(ex, req, resp): assert isinstance(ex, BaseException) assert isinstance(req, falcon.Request) @@ -210,7 +208,7 @@ def legacy_handler2(error_obj, request, response, params): def legacy_handler3(err, rq, rs, prms): check_args(err, rq, rs) - app = create_app(asgi=False) + app = util.create_app(asgi=False) app.add_route('/', ErroredClassResource()) client = testing.TestClient(app) @@ -225,17 +223,17 @@ def legacy_handler3(err, rq, rs, prms): client.simulate_get() client.simulate_head() - def test_handler_must_be_coroutine_for_asgi(self): + def test_handler_must_be_coroutine_for_asgi(self, util): async def legacy_handler(err, rq, rs, prms): pass - app = create_app(True) + app = util.create_app(True) - with disable_asgi_non_coroutine_wrapping(): + with util.disable_asgi_non_coroutine_wrapping(): with pytest.raises(ValueError): app.add_error_handler(Exception, capture_error) - def test_catch_http_no_route_error(self, asgi): + def test_catch_http_no_route_error(self, asgi, util): class Resource: def on_get(self, req, resp): raise falcon.HTTPNotFound() @@ -244,7 +242,7 @@ def capture_error(req, resp, ex, params): resp.set_header('X-name', ex.__class__.__name__) raise ex - app = create_app(asgi) + app = util.create_app(asgi) app.add_route('/', Resource()) app.add_error_handler(falcon.HTTPError, capture_error) @@ -275,8 +273,8 @@ def on_put(self, req, res): class TestNoBodyWithStatus: @pytest.fixture() - def body_client(self, asgi): - app = create_app(asgi=asgi) + def body_client(self, asgi, util): + app = util.create_app(asgi=asgi) app.add_route('/error', NoBodyResource()) def no_reps(req, resp, exception): @@ -318,8 +316,8 @@ def on_put(self, req, res): class TestCustomError: @pytest.fixture() - def body_client(self, asgi): - app = create_app(asgi=asgi) + def body_client(self, asgi, util): + app = util.create_app(asgi=asgi) app.add_route('/error', CustomErrorResource()) if asgi: diff --git a/tests/test_headers.py b/tests/test_headers.py index ccf877fd1..67a80e147 100644 --- a/tests/test_headers.py +++ b/tests/test_headers.py @@ -1,7 +1,6 @@ from collections import defaultdict from datetime import datetime -from _util import create_app # NOQA import pytest import falcon @@ -13,8 +12,8 @@ @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) return testing.TestClient(app) @@ -501,8 +500,8 @@ def test_default_media_type(self, client): ('text/plain', 'Hello ISO-8859-1!'), ], ) - def test_override_default_media_type(self, asgi, client, content_type, body): - client.app = create_app(asgi=asgi, media_type=content_type) + def test_override_default_media_type(self, asgi, util, client, content_type, body): + client.app = util.create_app(asgi=asgi, media_type=content_type) client.app.add_route('/', testing.SimpleTestResource(body=body)) result = client.simulate_get() @@ -510,10 +509,10 @@ def test_override_default_media_type(self, asgi, client, content_type, body): assert result.headers['Content-Type'] == content_type @pytest.mark.parametrize('asgi', [True, False]) - def test_override_default_media_type_missing_encoding(self, asgi, client): + def test_override_default_media_type_missing_encoding(self, asgi, util, client): body = '{"msg": "Hello Unicode! \U0001f638"}' - client.app = create_app(asgi=asgi, media_type='application/json') + client.app = util.create_app(asgi=asgi, media_type='application/json') client.app.add_route('/', testing.SimpleTestResource(body=body)) result = client.simulate_get() diff --git a/tests/test_http_custom_method_routing.py b/tests/test_http_custom_method_routing.py index 7271f4082..d7af60e07 100644 --- a/tests/test_http_custom_method_routing.py +++ b/tests/test_http_custom_method_routing.py @@ -2,8 +2,6 @@ import os import wsgiref.validate -from _util import create_app # NOQA -from _util import has_cython # NOQA import pytest import falcon @@ -33,10 +31,10 @@ def cleanup_constants(): @pytest.fixture -def custom_http_client(asgi, request, cleanup_constants, resource_things): +def custom_http_client(asgi, util, request, cleanup_constants, resource_things): falcon.constants.COMBINED_METHODS += FALCON_CUSTOM_HTTP_METHODS - app = create_app(asgi) + app = util.create_app(asgi) app.add_route('/things', resource_things) return testing.TestClient(app) @@ -62,7 +60,6 @@ def test_map_http_methods(custom_http_client, resource_things): assert 'BAR' not in method_map -@pytest.mark.skipif(has_cython, reason='Reloading modules on Cython does not work') @pytest.mark.parametrize( 'env_str,expected', [ @@ -74,7 +71,12 @@ def test_map_http_methods(custom_http_client, resource_things): (' foo , BAR ', ['FOO', 'BAR']), ], ) -def test_environment_override(cleanup_constants, resource_things, env_str, expected): +def test_environment_override( + util, cleanup_constants, resource_things, env_str, expected +): + if util.HAS_CYTHON: + pytest.skip(reason='Reloading modules on Cython does not work') + # Make sure we don't have anything in there for method in expected: assert method not in falcon.constants.COMBINED_METHODS diff --git a/tests/test_http_method_routing.py b/tests/test_http_method_routing.py index 529e4621d..cfc22a3cc 100644 --- a/tests/test_http_method_routing.py +++ b/tests/test_http_method_routing.py @@ -1,6 +1,5 @@ from functools import wraps -from _util import create_app # NOQA import pytest import falcon @@ -59,8 +58,8 @@ def resource_get_with_faulty_put(): @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) app.add_route('/stonewall', Stonewall()) diff --git a/tests/test_httperror.py b/tests/test_httperror.py index 313fb00e8..d05636773 100644 --- a/tests/test_httperror.py +++ b/tests/test_httperror.py @@ -4,7 +4,6 @@ import wsgiref.validate import xml.etree.ElementTree as et # noqa: I202 -from _util import create_app # NOQA import pytest import falcon @@ -18,8 +17,8 @@ @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) resource = FaultyResource() app.add_route('/fail', resource) @@ -734,8 +733,8 @@ def test_414_with_custom_kwargs(self, client): parsed_body = json.loads(response.content.decode()) assert parsed_body['code'] == code - def test_416(self, client, asgi): - client.app = create_app(asgi) + def test_416(self, client, asgi, util): + client.app = util.create_app(asgi) client.app.add_route('/416', RangeNotSatisfiableResource()) response = client.simulate_request(path='/416', headers={'accept': 'text/xml'}) diff --git a/tests/test_httpstatus.py b/tests/test_httpstatus.py index 3a031ffc6..ed17d0095 100644 --- a/tests/test_httpstatus.py +++ b/tests/test_httpstatus.py @@ -1,6 +1,5 @@ import http -from _util import create_app # NOQA import pytest import falcon @@ -9,16 +8,16 @@ from falcon.util.deprecation import AttributeRemovedError -@pytest.fixture(params=[True, False]) -def client(request): - app = create_app(asgi=request.param) +@pytest.fixture() +def client(asgi, util): + app = util.create_app(asgi) app.add_route('/status', TestStatusResource()) return testing.TestClient(app) -@pytest.fixture(params=[True, False]) -def hook_test_client(request): - app = create_app(asgi=request.param) +@pytest.fixture() +def hook_test_client(asgi, util): + app = util.create_app(asgi) app.add_route('/status', TestHookResource()) return testing.TestClient(app) @@ -202,8 +201,8 @@ def on_put(self, req, res): @pytest.fixture() -def body_client(asgi): - app = create_app(asgi=asgi) +def body_client(asgi, util): + app = util.create_app(asgi) app.add_route('/status', NoBodyResource()) return testing.TestClient(app) @@ -229,7 +228,7 @@ def test_body_is_set(self, body_client): @pytest.fixture() -def custom_status_client(asgi): +def custom_status_client(asgi, util): def client(status): class Resource: def on_get(self, req, resp): @@ -237,7 +236,7 @@ def on_get(self, req, resp): resp.data = b'Hello, World!' resp.status = status - app = create_app(asgi=asgi) + app = util.create_app(asgi) app.add_route('/status', Resource()) return testing.TestClient(app) diff --git a/tests/test_media_handlers.py b/tests/test_media_handlers.py index f2dbb96c8..e008248f5 100644 --- a/tests/test_media_handlers.py +++ b/tests/test_media_handlers.py @@ -3,7 +3,6 @@ import json import platform -from _util import create_app # NOQA import pytest import falcon @@ -167,7 +166,7 @@ def test_deserialization(asgi, func, body, expected): @pytest.mark.parametrize('dumps, loads', ALL_JSON_IMPL, ids=ALL_JSON_IMPL_IDS) @pytest.mark.parametrize('subclass', (True, False)) -def test_full_app(asgi, dumps, loads, subclass): +def test_full_app(asgi, util, dumps, loads, subclass): if subclass: class JSONHandlerSubclass(media.JSONHandler): @@ -180,7 +179,7 @@ class JSONHandlerSubclass(media.JSONHandler): handler = media.JSONHandler(dumps=dumps, loads=loads) assert handler._serialize_sync is not None assert handler._deserialize_sync is not None - app = create_app(asgi) + app = util.create_app(asgi) app.req_options.media_handlers[falcon.MEDIA_JSON] = handler app.resp_options.media_handlers[falcon.MEDIA_JSON] = handler @@ -212,8 +211,8 @@ def on_get(self, req, res): 'application/json; answer=42', ], ) -def test_deserialization_raises(asgi, handler_mt, monkeypatch_resolver): - app = create_app(asgi) +def test_deserialization_raises(asgi, util, handler_mt, monkeypatch_resolver): + app = util.create_app(asgi) class SuchException(Exception): pass @@ -282,8 +281,8 @@ async def on_post(self, req, resp): testing.simulate_post(app, '/', json={}) -def test_sync_methods_not_overridden(asgi): - app = create_app(asgi) +def test_sync_methods_not_overridden(asgi, util): + app = util.create_app(asgi) class FaultyHandler(media.BaseHandler): pass @@ -317,8 +316,8 @@ async def on_post(self, req, resp): assert result.status_code == 500 -def test_async_methods_not_overridden(): - app = create_app(asgi=True) +def test_async_methods_not_overridden(util): + app = util.create_app(asgi=True) class SimpleHandler(media.BaseHandler): def serialize(self, media, content_type): @@ -343,8 +342,8 @@ async def on_post(self, req, resp): assert result.json == doc -def test_async_handler_returning_none(): - app = create_app(asgi=True) +def test_async_handler_returning_none(util): + app = util.create_app(asgi=True) class SimpleHandler(media.BaseHandler): def serialize(self, media, content_type): @@ -370,8 +369,8 @@ async def on_post(self, req, resp): @pytest.mark.parametrize('monkeypatch_resolver', [True, False]) -def test_json_err_no_handler(asgi, monkeypatch_resolver): - app = create_app(asgi) +def test_json_err_no_handler(asgi, util, monkeypatch_resolver): + app = util.create_app(asgi) handlers = media.Handlers({falcon.MEDIA_URLENCODED: media.URLEncodedFormHandler()}) diff --git a/tests/test_media_multipart.py b/tests/test_media_multipart.py index 16b6b27e0..c600008a9 100644 --- a/tests/test_media_multipart.py +++ b/tests/test_media_multipart.py @@ -3,7 +3,6 @@ import os import random -from _util import create_app # NOQA: I100 import pytest import falcon @@ -401,7 +400,7 @@ async def on_post_mirror(self, req, resp): @pytest.fixture -def custom_client(asgi): +def custom_client(asgi, util): def _factory(options): multipart_handler = media.MultipartFormHandler() for key, value in options.items(): @@ -416,7 +415,7 @@ def _factory(options): if msgpack: resp_handlers[falcon.MEDIA_MSGPACK] = media.MessagePackHandler() - app = create_app(asgi) + app = util.create_app(asgi) app.req_options.media_handlers = media.Handlers(req_handlers) app.resp_options.media_handlers = media.Handlers(resp_handlers) diff --git a/tests/test_media_urlencoded.py b/tests/test_media_urlencoded.py index 45d38c583..4456096fa 100644 --- a/tests/test_media_urlencoded.py +++ b/tests/test_media_urlencoded.py @@ -1,6 +1,5 @@ import io -from _util import create_app # NOQA import pytest import falcon @@ -48,8 +47,8 @@ async def on_post(self, req, resp): @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) app.add_route('/media', MediaMirrorAsync() if asgi else MediaMirror()) return testing.TestClient(app) diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 6b5557738..28da56189 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -1,10 +1,5 @@ import json -try: - import cython -except ImportError: - cython = None -from _util import create_app # NOQA import pytest import falcon @@ -174,9 +169,9 @@ def setup_method(self, method): class TestRequestTimeMiddleware(TestMiddleware): - def test_skip_process_resource(self, asgi): + def test_skip_process_resource(self, asgi, util): global context - app = create_app(asgi, middleware=[RequestTimeMiddleware()]) + app = util.create_app(asgi, middleware=[RequestTimeMiddleware()]) app.add_route('/', MiddlewareClassResource()) client = testing.TestClient(app) @@ -188,7 +183,7 @@ def test_skip_process_resource(self, asgi): assert 'end_time' in context assert not context['req_succeeded'] - def test_add_invalid_middleware(self, asgi): + def test_add_invalid_middleware(self, asgi, util): """Test than an invalid class can not be added as middleware""" class InvalidMiddleware: @@ -197,24 +192,24 @@ def process_request(self, *args): mw_list = [RequestTimeMiddleware(), InvalidMiddleware] with pytest.raises(AttributeError): - create_app(asgi, middleware=mw_list) + util.create_app(asgi, middleware=mw_list) mw_list = [RequestTimeMiddleware(), 'InvalidMiddleware'] with pytest.raises(TypeError): - create_app(asgi, middleware=mw_list) + util.create_app(asgi, middleware=mw_list) mw_list = [{'process_request': 90}] with pytest.raises(TypeError): - create_app(asgi, middleware=mw_list) + util.create_app(asgi, middleware=mw_list) - def test_response_middleware_raises_exception(self, asgi): + def test_response_middleware_raises_exception(self, asgi, util): """Test that error in response middleware is propagated up""" class RaiseErrorMiddleware: def process_response(self, req, resp, resource): raise Exception('Always fail') - app = create_app(asgi, middleware=[RaiseErrorMiddleware()]) + app = util.create_app(asgi, middleware=[RaiseErrorMiddleware()]) app.add_route(TEST_ROUTE, MiddlewareClassResource()) client = testing.TestClient(app) @@ -223,10 +218,10 @@ def process_response(self, req, resp, resource): assert result.status_code == 500 @pytest.mark.parametrize('independent_middleware', [True, False]) - def test_log_get_request(self, independent_middleware, asgi): + def test_log_get_request(self, independent_middleware, asgi, util): """Test that Log middleware is executed""" global context - app = create_app( + app = util.create_app( asgi, middleware=[RequestTimeMiddleware()], independent_middleware=independent_middleware, @@ -253,14 +248,14 @@ def test_log_get_request(self, independent_middleware, asgi): class TestTransactionIdMiddleware(TestMiddleware): - def test_generate_trans_id_with_request(self, asgi): + def test_generate_trans_id_with_request(self, asgi, util): """Test that TransactionIdmiddleware is executed""" global context middleware = ( TransactionIdMiddlewareAsync() if asgi else TransactionIdMiddleware() ) - app = create_app(asgi, middleware=middleware) + app = util.create_app(asgi, middleware=middleware) app.add_route(TEST_ROUTE, MiddlewareClassResource()) client = testing.TestClient(app) @@ -275,7 +270,7 @@ def test_generate_trans_id_with_request(self, asgi): class TestSeveralMiddlewares(TestMiddleware): @pytest.mark.parametrize('independent_middleware', [True, False]) def test_generate_trans_id_and_time_with_request( - self, independent_middleware, asgi + self, independent_middleware, asgi, util ): # NOTE(kgriffs): We test both so that we can cover the code paths # where only a single middleware method is implemented by a @@ -284,7 +279,7 @@ def test_generate_trans_id_and_time_with_request( cresp = CaptureResponseMiddleware() global context - app = create_app( + app = util.create_app( asgi, independent_middleware=independent_middleware, # NOTE(kgriffs): Pass as a generic iterable to verify that works. @@ -319,9 +314,9 @@ def test_generate_trans_id_and_time_with_request( context['end_time'] >= context['start_time'] ), 'process_response not executed after request' - def test_legacy_middleware_called_with_correct_args(self, asgi): + def test_legacy_middleware_called_with_correct_args(self, asgi, util): global context - app = create_app(asgi, middleware=[ExecutedFirstMiddleware()]) + app = util.create_app(asgi, middleware=[ExecutedFirstMiddleware()]) app.add_route(TEST_ROUTE, MiddlewareClassResource()) client = testing.TestClient(app) @@ -330,9 +325,9 @@ def test_legacy_middleware_called_with_correct_args(self, asgi): assert isinstance(context['resp'], falcon.Response) assert isinstance(context['resource'], MiddlewareClassResource) - def test_middleware_execution_order(self, asgi): + def test_middleware_execution_order(self, asgi, util): global context - app = create_app( + app = util.create_app( asgi, independent_middleware=False, middleware=[ExecutedFirstMiddleware(), ExecutedLastMiddleware()], @@ -356,9 +351,9 @@ def test_middleware_execution_order(self, asgi): ] assert expectedExecutedMethods == context['executed_methods'] - def test_independent_middleware_execution_order(self, asgi): + def test_independent_middleware_execution_order(self, asgi, util): global context - app = create_app( + app = util.create_app( asgi, independent_middleware=True, middleware=[ExecutedFirstMiddleware(), ExecutedLastMiddleware()], @@ -382,7 +377,7 @@ def test_independent_middleware_execution_order(self, asgi): ] assert expectedExecutedMethods == context['executed_methods'] - def test_multiple_response_mw_throw_exception(self, asgi): + def test_multiple_response_mw_throw_exception(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -401,7 +396,7 @@ def process_response(self, req, resp, resource, req_succeeded): context['executed_methods'].append('process_response') context['req_succeeded'].append(req_succeeded) - app = create_app( + app = util.create_app( asgi, middleware=[ ProcessResponseMiddleware(), @@ -423,7 +418,7 @@ def process_response(self, req, resp, resource, req_succeeded): assert context['executed_methods'] == expected_methods assert context['req_succeeded'] == [True, False, False] - def test_inner_mw_throw_exception(self, asgi): + def test_inner_mw_throw_exception(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -434,7 +429,7 @@ class RaiseErrorMiddleware: def process_request(self, req, resp): raise MyException('Always fail') - app = create_app( + app = util.create_app( asgi, middleware=[ TransactionIdMiddleware(), @@ -468,7 +463,7 @@ def process_request(self, req, resp): # middleware methods. assert 'end_time' not in context - def test_inner_mw_throw_exception_while_processing_resp(self, asgi): + def test_inner_mw_throw_exception_while_processing_resp(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -479,7 +474,7 @@ class RaiseErrorMiddleware: def process_response(self, req, resp, resource, req_succeeded): raise MyException('Always fail') - app = create_app( + app = util.create_app( asgi, middleware=[ TransactionIdMiddleware(), @@ -513,7 +508,7 @@ def process_response(self, req, resp, resource, req_succeeded): # middleware methods. assert 'end_time' not in context - def test_inner_mw_with_ex_handler_throw_exception(self, asgi): + def test_inner_mw_with_ex_handler_throw_exception(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -521,7 +516,7 @@ class RaiseErrorMiddleware: def process_request(self, req, resp, resource): raise Exception('Always fail') - app = create_app( + app = util.create_app( asgi, middleware=[ TransactionIdMiddleware(), @@ -547,7 +542,7 @@ def handler(req, resp, ex, params): assert 'end_time' in context assert 'error_handler' in context - def test_outer_mw_with_ex_handler_throw_exception(self, asgi): + def test_outer_mw_with_ex_handler_throw_exception(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -555,7 +550,7 @@ class RaiseErrorMiddleware: def process_request(self, req, resp): raise Exception('Always fail') - app = create_app( + app = util.create_app( asgi, middleware=[ TransactionIdMiddleware(), @@ -581,7 +576,7 @@ def handler(req, resp, ex, params): assert 'end_time' in context assert 'error_handler' in context - def test_order_mw_executed_when_exception_in_resp(self, asgi): + def test_order_mw_executed_when_exception_in_resp(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -589,7 +584,7 @@ class RaiseErrorMiddleware: def process_response(self, req, resp, resource): raise Exception('Always fail') - app = create_app( + app = util.create_app( asgi, middleware=[ ExecutedFirstMiddleware(), @@ -619,7 +614,7 @@ def handler(req, resp, ex, params): ] assert expectedExecutedMethods == context['executed_methods'] - def test_order_independent_mw_executed_when_exception_in_resp(self, asgi): + def test_order_independent_mw_executed_when_exception_in_resp(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -627,7 +622,7 @@ class RaiseErrorMiddleware: def process_response(self, req, resp, resource): raise Exception('Always fail') - app = create_app( + app = util.create_app( asgi, independent_middleware=True, middleware=[ @@ -658,7 +653,7 @@ def handler(req, resp, ex, params): ] assert expectedExecutedMethods == context['executed_methods'] - def test_order_mw_executed_when_exception_in_req(self, asgi): + def test_order_mw_executed_when_exception_in_req(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -672,7 +667,7 @@ async def process_request(self, req, resp): rem = RaiseErrorMiddlewareAsync() if asgi else RaiseErrorMiddleware() - app = create_app( + app = util.create_app( asgi, middleware=[ExecutedFirstMiddleware(), rem, ExecutedLastMiddleware()] ) @@ -694,7 +689,7 @@ def handler(req, resp, ex, params): ] assert expectedExecutedMethods == context['executed_methods'] - def test_order_independent_mw_executed_when_exception_in_req(self, asgi): + def test_order_independent_mw_executed_when_exception_in_req(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -708,7 +703,7 @@ async def process_request(self, req, resp): rem = RaiseErrorMiddlewareAsync() if asgi else RaiseErrorMiddleware() - app = create_app( + app = util.create_app( asgi, independent_middleware=True, middleware=[ExecutedFirstMiddleware(), rem, ExecutedLastMiddleware()], @@ -732,7 +727,7 @@ def handler(req, resp, ex, params): ] assert expectedExecutedMethods == context['executed_methods'] - def test_order_mw_executed_when_exception_in_rsrc(self, asgi): + def test_order_mw_executed_when_exception_in_rsrc(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -748,7 +743,7 @@ async def process_resource_async(self, req, resp, resource): rem = RaiseErrorMiddlewareAsync() if asgi else RaiseErrorMiddleware() - app = create_app( + app = util.create_app( asgi, middleware=[ExecutedFirstMiddleware(), rem, ExecutedLastMiddleware()] ) @@ -772,7 +767,7 @@ def handler(req, resp, ex, params): ] assert expectedExecutedMethods == context['executed_methods'] - def test_order_independent_mw_executed_when_exception_in_rsrc(self, asgi): + def test_order_independent_mw_executed_when_exception_in_rsrc(self, asgi, util): """Test that error in inner middleware leaves""" global context @@ -786,7 +781,7 @@ async def process_resource(self, req, resp, resource): rem = RaiseErrorMiddlewareAsync() if asgi else RaiseErrorMiddleware() - app = create_app( + app = util.create_app( asgi, independent_middleware=True, middleware=[ExecutedFirstMiddleware(), rem, ExecutedLastMiddleware()], @@ -814,9 +809,9 @@ def handler(req, resp, ex, params): class TestRemoveBasePathMiddleware(TestMiddleware): - def test_base_path_is_removed_before_routing(self, asgi): + def test_base_path_is_removed_before_routing(self, asgi, util): """Test that RemoveBasePathMiddleware is executed before routing""" - app = create_app(asgi, middleware=RemoveBasePathMiddleware()) + app = util.create_app(asgi, middleware=RemoveBasePathMiddleware()) # We dont include /base_path as it will be removed in middleware app.add_route('/sub_path', MiddlewareClassResource()) @@ -831,7 +826,7 @@ def test_base_path_is_removed_before_routing(self, asgi): class TestResourceMiddleware(TestMiddleware): @pytest.mark.parametrize('independent_middleware', [True, False]) - def test_can_access_resource_params(self, asgi, independent_middleware): + def test_can_access_resource_params(self, asgi, util, independent_middleware): """Test that params can be accessed from within process_resource""" global context @@ -839,7 +834,7 @@ class Resource: def on_get(self, req, resp, **params): resp.text = json.dumps(params) - app = create_app( + app = util.create_app( asgi, middleware=AccessParamsMiddleware(), independent_middleware=independent_middleware, @@ -855,7 +850,7 @@ def on_get(self, req, resp, **params): class TestEmptySignatureMiddleware(TestMiddleware): - def test_dont_need_params_in_signature(self, asgi): + def test_dont_need_params_in_signature(self, asgi, util): """ Verify that we don't need parameters in the process_* signatures (for side-effect-only middlewares, mostly). Makes no difference on py27 @@ -863,13 +858,13 @@ def test_dont_need_params_in_signature(self, asgi): https://github.com/falconry/falcon/issues/1254 """ - create_app(asgi, middleware=EmptySignatureMiddleware()) + util.create_app(asgi, middleware=EmptySignatureMiddleware()) class TestErrorHandling(TestMiddleware): - def test_error_composed_before_resp_middleware_called(self, asgi): + def test_error_composed_before_resp_middleware_called(self, asgi, util): mw = CaptureResponseMiddleware() - app = create_app(asgi, middleware=mw) + app = util.create_app(asgi, middleware=mw) app.add_route('/', MiddlewareClassResource()) client = testing.TestClient(app) @@ -887,9 +882,9 @@ def test_error_composed_before_resp_middleware_called(self, asgi): assert isinstance(mw.req, falcon.Request) assert isinstance(mw.resource, MiddlewareClassResource) - def test_http_status_raised_from_error_handler(self, asgi): + def test_http_status_raised_from_error_handler(self, asgi, util): mw = CaptureResponseMiddleware() - app = create_app(asgi, middleware=mw) + app = util.create_app(asgi, middleware=mw) app.add_route('/', MiddlewareClassResource()) client = testing.TestClient(app) @@ -921,13 +916,13 @@ class TestShortCircuiting(TestMiddleware): def setup_method(self, method): super(TestShortCircuiting, self).setup_method(method) - def _make_client(self, asgi, independent_middleware=True): + def _make_client(self, asgi, util, independent_middleware=True): mw = [ RequestTimeMiddleware(), ResponseCacheMiddlware(), TransactionIdMiddleware(), ] - app = create_app( + app = util.create_app( asgi, middleware=mw, independent_middleware=independent_middleware ) app.add_route('/', MiddlewareClassResource()) @@ -936,8 +931,8 @@ def _make_client(self, asgi, independent_middleware=True): return testing.TestClient(app) - def test_process_request_not_cached(self, asgi): - response = self._make_client(asgi).simulate_get('/') + def test_process_request_not_cached(self, asgi, util): + response = self._make_client(asgi, util).simulate_get('/') assert response.status == falcon.HTTP_200 assert response.json == _EXPECTED_BODY assert 'transaction_id' in context @@ -946,8 +941,8 @@ def test_process_request_not_cached(self, asgi): assert 'end_time' in context @pytest.mark.parametrize('independent_middleware', [True, False]) - def test_process_request_cached(self, asgi, independent_middleware): - response = self._make_client(asgi, independent_middleware).simulate_get( + def test_process_request_cached(self, asgi, util, independent_middleware): + response = self._make_client(asgi, util, independent_middleware).simulate_get( '/cached' ) assert response.status == falcon.HTTP_200 @@ -969,8 +964,8 @@ def test_process_request_cached(self, asgi, independent_middleware): assert 'end_time' in context @pytest.mark.parametrize('independent_middleware', [True, False]) - def test_process_resource_cached(self, asgi, independent_middleware): - response = self._make_client(asgi, independent_middleware).simulate_get( + def test_process_resource_cached(self, asgi, util, independent_middleware): + response = self._make_client(asgi, util, independent_middleware).simulate_get( '/cached/resource' ) assert response.status == falcon.HTTP_200 @@ -1008,21 +1003,20 @@ class TestCORSMiddlewareWithAnotherMiddleware(TestMiddleware): ], ) def test_api_initialization_with_cors_enabled_and_middleware_param( - self, mw, asgi, allowed + self, mw, asgi, util, allowed ): if allowed: - app = create_app(asgi, middleware=mw, cors_enable=True) + app = util.create_app(asgi, middleware=mw, cors_enable=True) app.add_route('/', TestCorsResource()) client = testing.TestClient(app) result = client.simulate_get(headers={'Origin': 'localhost'}) assert result.headers['Access-Control-Allow-Origin'] == '*' else: with pytest.raises(ValueError, match='CORSMiddleware'): - app = create_app(asgi, middleware=mw, cors_enable=True) + app = util.create_app(asgi, middleware=mw, cors_enable=True) -@pytest.mark.skipif(cython, reason='Cythonized coroutine functions cannot be detected') -def test_async_postfix_method_must_be_coroutine(): +def test_async_postfix_method_must_be_coroutine(util): class FaultyComponentA: def process_request_async(self, req, resp): pass @@ -1035,6 +1029,9 @@ class FaultyComponentC: def process_response_async(self, req, resp, resource, req_succeeded): pass + if util.HAS_CYTHON: + pytest.skip(reason='Cythonized coroutine functions cannot be detected') + for mw in (FaultyComponentA, FaultyComponentB, FaultyComponentC): with pytest.raises(falcon.errors.CompatibilityError): - create_app(True, middleware=[mw()]) + util.create_app(True, middleware=[mw()]) diff --git a/tests/test_query_params.py b/tests/test_query_params.py index 239daf5c2..414a58661 100644 --- a/tests/test_query_params.py +++ b/tests/test_query_params.py @@ -3,7 +3,6 @@ import json from uuid import UUID -from _util import create_app # NOQA import pytest import falcon @@ -44,8 +43,8 @@ def resource(): @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) if not asgi: app.req_options.auto_parse_form_urlencoded = True @@ -1017,8 +1016,8 @@ def test_explicitly_disable_auto_parse(self, client, resource): req = resource.captured_req assert req.get_param('q') is None - def test_asgi_raises_error(self, resource): - app = create_app(asgi=True) + def test_asgi_raises_error(self, util, resource): + app = util.create_app(asgi=True) app.add_route('/', resource) app.req_options.auto_parse_form_urlencoded = True @@ -1027,10 +1026,9 @@ def test_asgi_raises_error(self, resource): assert 'RequestOptions.auto_parse_form_urlencoded' in exc_info.value.args[0] -@pytest.mark.parametrize('asgi', [True, False]) class TestPostQueryParamsDefaultBehavior: - def test_dont_auto_parse_by_default(self, asgi): - app = create_app(asgi) + def test_dont_auto_parse_by_default(self, asgi, util): + app = util.create_app(asgi) resource = testing.SimpleTestResource() app.add_route('/', resource) diff --git a/tests/test_redirects.py b/tests/test_redirects.py index 7138e084a..513c3a5b5 100644 --- a/tests/test_redirects.py +++ b/tests/test_redirects.py @@ -1,4 +1,3 @@ -from _util import create_app # NOQA import pytest import falcon @@ -6,8 +5,8 @@ @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) resource = RedirectingResource() app.add_route('/', resource) @@ -16,8 +15,8 @@ def client(asgi): @pytest.fixture -def client_exercising_headers(asgi): - app = create_app(asgi) +def client_exercising_headers(asgi, util): + app = util.create_app(asgi) resource = RedirectingResourceWithHeaders() app.add_route('/', resource) diff --git a/tests/test_request_access_route.py b/tests/test_request_access_route.py index 96de6a87e..c49b250eb 100644 --- a/tests/test_request_access_route.py +++ b/tests/test_request_access_route.py @@ -1,23 +1,22 @@ -from _util import create_req # NOQA import pytest from falcon.request import Request import falcon.testing as testing -def test_remote_addr_default(asgi): - req = create_req(asgi) +def test_remote_addr_default(asgi, util): + req = util.create_req(asgi) assert req.remote_addr == '127.0.0.1' -def test_remote_addr_non_default(asgi): +def test_remote_addr_non_default(asgi, util): client_ip = '10.132.0.5' - req = create_req(asgi, remote_addr=client_ip) + req = util.create_req(asgi, remote_addr=client_ip) assert req.remote_addr == client_ip -def test_remote_addr_only(asgi): - req = create_req( +def test_remote_addr_only(asgi, util): + req = util.create_req( asgi, host='example.com', path='/access_route', @@ -34,8 +33,8 @@ def test_remote_addr_only(asgi): assert req.remote_addr == '127.0.0.1' -def test_rfc_forwarded(asgi): - req = create_req( +def test_rfc_forwarded(asgi, util): + req = util.create_req( asgi, host='example.com', path='/access_route', @@ -69,8 +68,8 @@ def test_rfc_forwarded(asgi): assert req.access_route == compares -def test_malformed_rfc_forwarded(asgi): - req = create_req( +def test_malformed_rfc_forwarded(asgi, util): + req = util.create_req( asgi, host='example.com', path='/access_route', headers={'Forwarded': 'for'} ) @@ -81,13 +80,13 @@ def test_malformed_rfc_forwarded(asgi): @pytest.mark.parametrize('include_localhost', [True, False]) -def test_x_forwarded_for(asgi, include_localhost): +def test_x_forwarded_for(asgi, util, include_localhost): forwarded_for = '192.0.2.43, 2001:db8:cafe::17,unknown, _hidden, 203.0.113.60' if include_localhost: forwarded_for += ', 127.0.0.1' - req = create_req( + req = util.create_req( asgi, host='example.com', path='/access_route', @@ -104,8 +103,8 @@ def test_x_forwarded_for(asgi, include_localhost): ] -def test_x_real_ip(asgi): - req = create_req( +def test_x_real_ip(asgi, util): + req = util.create_req( asgi, host='example.com', path='/access_route', @@ -116,8 +115,8 @@ def test_x_real_ip(asgi): @pytest.mark.parametrize('remote_addr', ['10.0.0.1', '98.245.211.177']) -def test_remote_addr(asgi, remote_addr): - req = create_req( +def test_remote_addr(asgi, util, remote_addr): + req = util.create_req( asgi, host='example.com', path='/access_route', diff --git a/tests/test_request_attrs.py b/tests/test_request_attrs.py index 5d8f341dd..14efb1b10 100644 --- a/tests/test_request_attrs.py +++ b/tests/test_request_attrs.py @@ -1,7 +1,6 @@ import datetime import itertools -from _util import create_req # NOQA import pytest import falcon @@ -32,6 +31,13 @@ def _make_etag(value, is_weak=False): return etag +# NOTE(vytas): create_req is very heavily used in this module in unittest-style +# classes, so we simply recreate the function here. +def create_req(asgi, options=None, **environ_or_scope_kwargs): + create_method = testing.create_asgi_req if asgi else testing.create_req + return create_method(options=options, **environ_or_scope_kwargs) + + def test_missing_qs(): env = testing.create_environ() if 'QUERY_STRING' in env: @@ -49,7 +55,6 @@ def test_app_missing(): assert req.app == '' -@pytest.mark.parametrize('asgi', [True, False]) class TestRequestAttributes: def setup_method(self, method): asgi = self._item.callspec.getparam('asgi') diff --git a/tests/test_request_forwarded.py b/tests/test_request_forwarded.py index 2ab60bb28..b5704c561 100644 --- a/tests/test_request_forwarded.py +++ b/tests/test_request_forwarded.py @@ -1,9 +1,8 @@ -from _util import create_req # NOQA import pytest -def test_no_forwarded_headers(asgi): - req = create_req( +def test_no_forwarded_headers(asgi, util): + req = util.create_req( asgi, host='example.com', path='/languages', root_path='backoffice' ) @@ -13,8 +12,8 @@ def test_no_forwarded_headers(asgi): assert req.forwarded_prefix == 'http://example.com/backoffice' -def test_no_forwarded_headers_with_port(asgi): - req = create_req( +def test_no_forwarded_headers_with_port(asgi, util): + req = util.create_req( asgi, host='example.com', port=8000, path='/languages', root_path='backoffice' ) @@ -24,8 +23,8 @@ def test_no_forwarded_headers_with_port(asgi): assert req.forwarded_prefix == 'http://example.com:8000/backoffice' -def test_x_forwarded_host(asgi): - req = create_req( +def test_x_forwarded_host(asgi, util): + req = util.create_req( asgi, host='suchproxy.suchtesting.com', path='/languages', @@ -40,8 +39,8 @@ def test_x_forwarded_host(asgi): assert req.forwarded_prefix == 'http://something.org' # Check cached value -def test_x_forwarded_host_with_port(asgi): - req = create_req( +def test_x_forwarded_host_with_port(asgi, util): + req = util.create_req( asgi, host='suchproxy.suchtesting.com', path='/languages', @@ -56,8 +55,8 @@ def test_x_forwarded_host_with_port(asgi): assert req.forwarded_prefix == 'http://something.org:8000' # Check cached value -def test_x_forwarded_proto(asgi): - req = create_req( +def test_x_forwarded_proto(asgi, util): + req = util.create_req( asgi, host='example.org', path='/languages', @@ -71,8 +70,8 @@ def test_x_forwarded_proto(asgi): assert req.forwarded_prefix == 'https://example.org' -def test_forwarded_host(asgi): - req = create_req( +def test_forwarded_host(asgi, util): + req = util.create_req( asgi, host='suchproxy02.suchtesting.com', path='/languages', @@ -95,8 +94,8 @@ def test_forwarded_host(asgi): assert req.forwarded_prefix == 'http://something.org' -def test_forwarded_invalid(asgi): - req = create_req( +def test_forwarded_invalid(asgi, util): + req = util.create_req( asgi, host='suchproxy02.suchtesting.com', path='/languages', @@ -111,8 +110,8 @@ def test_forwarded_invalid(asgi): assert req.forwarded_prefix == 'http://suchproxy02.suchtesting.com' -def test_forwarded_multiple_params(asgi): - req = create_req( +def test_forwarded_multiple_params(asgi, util): + req = util.create_req( asgi, host='suchproxy02.suchtesting.com', path='/languages', @@ -143,8 +142,8 @@ def test_forwarded_multiple_params(asgi): assert req.forwarded_prefix == 'https://something.org' -def test_forwarded_missing_first_hop_host(asgi): - req = create_req( +def test_forwarded_missing_first_hop_host(asgi, util): + req = util.create_req( asgi, host='suchproxy02.suchtesting.com', path='/languages', @@ -165,8 +164,8 @@ def test_forwarded_missing_first_hop_host(asgi): assert req.forwarded_prefix == 'http://suchproxy02.suchtesting.com/doge' -def test_forwarded_quote_escaping(asgi): - req = create_req( +def test_forwarded_quote_escaping(asgi, util): + req = util.create_req( asgi, host='suchproxy02.suchtesting.com', path='/languages', @@ -188,8 +187,8 @@ def test_forwarded_quote_escaping(asgi): ('for=1.2.3.4;by="4.3.\\2\\.1" thing="blah"', '4.3.2.1'), ], ) -def test_escape_malformed_requests(forwarded, expected_dest, asgi): - req = create_req( +def test_escape_malformed_requests(forwarded, expected_dest, asgi, util): + req = util.create_req( asgi, host='suchproxy02.suchtesting.com', path='/languages', diff --git a/tests/test_request_media.py b/tests/test_request_media.py index 79d5ba620..f262caeff 100644 --- a/tests/test_request_media.py +++ b/tests/test_request_media.py @@ -1,6 +1,5 @@ import json -from _util import create_app # NOQA import pytest import falcon @@ -8,6 +7,7 @@ from falcon import media from falcon import testing from falcon import util +import falcon.asgi try: import msgpack # type: ignore @@ -21,7 +21,8 @@ def create_client(asgi, handlers=None, resource=None): testing.SimpleTestResourceAsync() if asgi else testing.SimpleTestResource() ) - app = create_app(asgi) + app_cls = falcon.asgi.App if asgi else falcon.App + app = app_cls() app.add_route('/', resource) if handlers: diff --git a/tests/test_response.py b/tests/test_response.py index f8e370eb1..aa349ee2c 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,15 +1,14 @@ from unittest.mock import MagicMock -from _util import create_resp # NOQA import pytest from falcon import MEDIA_TEXT from falcon import ResponseOptions -@pytest.fixture(params=[True, False]) -def resp(request): - return create_resp(asgi=request.param) +@pytest.fixture() +def resp(asgi, util): + return util.create_resp(asgi) def test_response_set_content_type_set(resp): diff --git a/tests/test_response_body.py b/tests/test_response_body.py index 2f1e9027e..2326389b0 100644 --- a/tests/test_response_body.py +++ b/tests/test_response_body.py @@ -1,5 +1,3 @@ -from _util import create_app # NOQA -from _util import create_resp # NOQA import pytest import falcon @@ -8,8 +6,8 @@ @pytest.fixture -def resp(asgi): - return create_resp(asgi) +def resp(asgi, util): + return util.create_resp(asgi) def test_append_body(resp): @@ -36,14 +34,14 @@ def test_response_repr(resp): assert resp.__repr__() == _repr -def test_content_length_set_on_head_with_no_body(asgi): +def test_content_length_set_on_head_with_no_body(asgi, util): class NoBody: def on_get(self, req, resp): pass on_head = on_get - app = create_app(asgi) + app = util.create_app(asgi) app.add_route('/', NoBody()) result = testing.simulate_head(app, '/') @@ -53,7 +51,7 @@ def on_get(self, req, resp): @pytest.mark.parametrize('method', ['GET', 'HEAD']) -def test_content_length_not_set_when_streaming_response(asgi, method): +def test_content_length_not_set_when_streaming_response(asgi, util, method): class SynthesizedHead: def on_get(self, req, resp): def words(): @@ -87,7 +85,7 @@ async def __anext__(self): on_head = on_get - app = create_app(asgi) + app = util.create_app(asgi) app.add_route('/', SynthesizedHeadAsync() if asgi else SynthesizedHead()) result = testing.simulate_request(app, method) @@ -107,20 +105,20 @@ def on_get(self, req, resp): resp.status = falcon.HTTP_725 -def test_unsupported_response_content_type(asgi): - app = create_app(asgi) +def test_unsupported_response_content_type(asgi, util): + app = util.create_app(asgi) app.add_route('/test.mal', CodeResource()) resp = testing.simulate_get(app, '/test.mal') assert resp.status_code == 415 -def test_response_body_rendition_error(asgi): +def test_response_body_rendition_error(asgi, util): class MalbolgeHandler(falcon.media.BaseHandler): def serialize(self, media, content_type): raise falcon.HTTPError(falcon.HTTP_753) - app = create_app(asgi) + app = util.create_app(asgi) app.resp_options.media_handlers['text/x-malbolge'] = MalbolgeHandler() app.add_route('/test.mal', CodeResource()) diff --git a/tests/test_sink_and_static.py b/tests/test_sink_and_static.py index c87a2c53e..9bf92e878 100644 --- a/tests/test_sink_and_static.py +++ b/tests/test_sink_and_static.py @@ -1,4 +1,3 @@ -from _util import create_app # NOQA import pytest import falcon @@ -14,12 +13,12 @@ async def sink_async(req, resp, **kw): @pytest.fixture -def client(asgi, tmp_path): +def client(asgi, util, tmp_path): file = tmp_path / 'file.txt' file.write_text('foo bar') def make(sink_before_static_route): - app = create_app(asgi=asgi, sink_before_static_route=sink_before_static_route) + app = util.create_app(asgi, sink_before_static_route=sink_before_static_route) app.add_sink(sink_async if asgi else sink, '/sink') app.add_static_route('/sink/static', str(tmp_path)) diff --git a/tests/test_sinks.py b/tests/test_sinks.py index ca241151f..1cfb24030 100644 --- a/tests/test_sinks.py +++ b/tests/test_sinks.py @@ -1,7 +1,5 @@ import re -from _util import create_app # NOQA -from _util import disable_asgi_non_coroutine_wrapping # NOQA import pytest import falcon @@ -50,8 +48,8 @@ def sink(asgi): @pytest.fixture -def client(asgi): - app = create_app(asgi) +def client(asgi, util): + app = util.create_app(asgi) return testing.TestClient(app) @@ -163,9 +161,9 @@ def test_add_async_sink(self, client, asgi): client.app.add_sink(async_kitchen_sink, '/features') self._verify_kitchen_sink(client) - def test_add_sync_sink(self, client, asgi): + def test_add_sync_sink(self, client, asgi, util): if asgi: - with disable_asgi_non_coroutine_wrapping(): + with util.disable_asgi_non_coroutine_wrapping(): with pytest.raises(falcon.CompatibilityError): client.app.add_sink(kitchen_sink) else: diff --git a/tests/test_static.py b/tests/test_static.py index a6546e2a5..cfeffc3b3 100644 --- a/tests/test_static.py +++ b/tests/test_static.py @@ -4,7 +4,6 @@ import pathlib import posixpath -import _util # NOQA import pytest import falcon @@ -30,11 +29,11 @@ def normalize_path(path): @pytest.fixture() -def client(asgi, monkeypatch): +def client(asgi, util, monkeypatch): def add_static_route_normalized(obj, prefix, directory, **kwargs): add_static_route_orig(obj, prefix, normalize_path(directory), **kwargs) - app = _util.create_app(asgi=asgi) + app = util.create_app(asgi=asgi) app_cls = type(app) add_static_route_orig = app_cls.add_static_route @@ -135,14 +134,14 @@ def __init__(self, size): '/static/\ufffdsomething', ], ) -def test_bad_path(asgi, uri, patch_open): +def test_bad_path(asgi, util, uri, patch_open): patch_open(b'') sr = create_sr(asgi, '/static', '/var/www/statics') - req = _util.create_req(asgi, host='test.com', path=uri, root_path='statics') + req = util.create_req(asgi, host='test.com', path=uri, root_path='statics') - resp = _util.create_resp(asgi) + resp = util.create_resp(asgi) with pytest.raises(falcon.HTTPNotFound): if asgi: @@ -228,7 +227,7 @@ def test_invalid_args_fallback_filename(client, default): ), ], ) -def test_good_path(asgi, uri_prefix, uri_path, expected_path, mtype, patch_open): +def test_good_path(asgi, util, uri_prefix, uri_path, expected_path, mtype, patch_open): patch_open() sr = create_sr(asgi, uri_prefix, '/var/www/statics') @@ -236,9 +235,9 @@ def test_good_path(asgi, uri_prefix, uri_path, expected_path, mtype, patch_open) req_path = uri_prefix[:-1] if uri_prefix.endswith('/') else uri_prefix req_path += uri_path - req = _util.create_req(asgi, host='test.com', path=req_path, root_path='statics') + req = util.create_req(asgi, host='test.com', path=req_path, root_path='statics') - resp = _util.create_resp(asgi) + resp = util.create_resp(asgi) if asgi: @@ -362,15 +361,15 @@ def test_bad_range_requests(client, range_header, exp_status, patch_open): assert response.headers.get('Content-Range') == 'bytes */16' -def test_pathlib_path(asgi, patch_open): +def test_pathlib_path(asgi, util, patch_open): patch_open() sr = create_sr(asgi, '/static/', pathlib.Path('/var/www/statics')) req_path = '/static/css/test.css' - req = _util.create_req(asgi, host='test.com', path=req_path, root_path='statics') + req = util.create_req(asgi, host='test.com', path=req_path, root_path='statics') - resp = _util.create_resp(asgi) + resp = util.create_resp(asgi) if asgi: @@ -470,7 +469,15 @@ def test_downloadable_not_found(client): ) @pytest.mark.parametrize('downloadable', [True, False]) def test_fallback_filename( - asgi, uri, default, expected, content_type, downloadable, patch_open, monkeypatch + asgi, + util, + uri, + default, + expected, + content_type, + downloadable, + patch_open, + monkeypatch, ): def validate(path): if normalize_path(default) not in path: @@ -490,8 +497,8 @@ def validate(path): req_path = '/static/' + uri - req = _util.create_req(asgi, host='test.com', path=req_path, root_path='statics') - resp = _util.create_resp(asgi) + req = util.create_req(asgi, host='test.com', path=req_path, root_path='statics') + resp = util.create_resp(asgi) if asgi: diff --git a/tests/test_testing.py b/tests/test_testing.py index 0cf4a3d3a..dda7e1880 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,4 +1,3 @@ -from _util import create_app # NOQA: I100 import pytest import falcon @@ -180,12 +179,12 @@ def test_missing_header_is_none(): @pytest.mark.parametrize( 'method', ['DELETE', 'GET', 'HEAD', 'LOCK', 'OPTIONS', 'PATCH', 'POST', 'PUT'] ) -def test_client_simulate_aliases(asgi, method): +def test_client_simulate_aliases(asgi, method, util): def capture_method(req, resp): resp.content_type = falcon.MEDIA_TEXT resp.text = req.method - app = create_app(asgi) + app = util.create_app(asgi) app.add_sink(capture_method) client = testing.TestClient(app) diff --git a/tests/test_uri_converters.py b/tests/test_uri_converters.py index 9b75b46a3..14919fd88 100644 --- a/tests/test_uri_converters.py +++ b/tests/test_uri_converters.py @@ -3,7 +3,6 @@ import string import uuid -from _util import as_params import pytest from falcon.routing import converters @@ -13,6 +12,15 @@ _TEST_UUID_STR_SANS_HYPHENS = _TEST_UUID_STR.replace('-', '') +def _as_params(*values, prefix=''): + # NOTE(caselit): each value must be a tuple/list even when using one + # single argument + return [ + pytest.param(*value, id=f'{prefix}_{i}' if prefix else f'{i}') + for i, value in enumerate(values, 1) + ] + + @pytest.mark.parametrize( 'value, num_digits, min, max, expected', [ @@ -150,7 +158,7 @@ def test_datetime_converter_default_format(): @pytest.mark.parametrize( 'value, expected', - as_params( + _as_params( (_TEST_UUID_STR, _TEST_UUID), (_TEST_UUID_STR.replace('-', '', 1), _TEST_UUID), (_TEST_UUID_STR_SANS_HYPHENS, _TEST_UUID), diff --git a/tests/test_uri_templates.py b/tests/test_uri_templates.py index 3c7805cb7..b596177c2 100644 --- a/tests/test_uri_templates.py +++ b/tests/test_uri_templates.py @@ -9,8 +9,6 @@ import math import uuid -from _util import as_params # NOQA -from _util import create_app # NOQA import pytest import falcon @@ -24,6 +22,15 @@ _TEST_UUID_STR_SANS_HYPHENS = _TEST_UUID_STR.replace('-', '') +def _as_params(*values, prefix=''): + # NOTE(caselit): each value must be a tuple/list even when using one + # single argument + return [ + pytest.param(*value, id=f'{prefix}_{i}' if prefix else f'{i}') + for i, value in enumerate(values, 1) + ] + + class IDResource: def __init__(self): self.id = None @@ -134,8 +141,8 @@ def resource(): @pytest.fixture -def client(asgi): - return testing.TestClient(create_app(asgi)) +def client(asgi, util): + return testing.TestClient(util.create_app(asgi)) def test_root_path(client, resource): @@ -313,7 +320,7 @@ def test_datetime_converter(client, resource, uri_template, path, dt_expected): @pytest.mark.parametrize( 'uri_template, path, expected', - as_params( + _as_params( ( '/widgets/{widget_id:uuid}', '/widgets/' + _TEST_UUID_STR, diff --git a/tests/test_utils.py b/tests/test_utils.py index 1f267bff6..b566d0d0b 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -8,18 +8,16 @@ from urllib.parse import quote from urllib.parse import unquote_plus -from _util import create_app # NOQA -from _util import to_coroutine # NOQA import pytest import falcon from falcon import media from falcon import testing -from falcon import util from falcon.constants import MEDIA_JSON from falcon.constants import MEDIA_MSGPACK from falcon.constants import MEDIA_URLENCODED from falcon.constants import MEDIA_YAML +import falcon.util from falcon.util import deprecation from falcon.util import misc from falcon.util import structures @@ -32,8 +30,8 @@ @pytest.fixture -def app(asgi): - return create_app(asgi) +def app(asgi, util): + return util.create_app(asgi) def _arbitrary_uris(count, length): @@ -109,7 +107,7 @@ def setup_method(self, method): def test_deprecated_decorator(self): msg = 'Please stop using this thing. It is going away.' - @util.deprecated(msg) + @falcon.util.deprecated(msg) def old_thing(): pass @@ -660,7 +658,7 @@ def test_misc_isascii(self): falcon.HTTP_METHODS * 2, ), ) -def test_simulate_request_protocol(asgi, protocol, method): +def test_simulate_request_protocol(asgi, protocol, method, util): sink_called = [False] def sink(req, resp): @@ -668,9 +666,9 @@ def sink(req, resp): assert req.protocol == protocol if asgi: - sink = to_coroutine(sink) + sink = util.to_coroutine(sink) - app = create_app(asgi) + app = util.create_app(asgi) app.add_sink(sink, '/test') client = testing.TestClient(app) @@ -696,16 +694,16 @@ def sink(req, resp): testing.simulate_delete, ], ) -def test_simulate_free_functions(asgi, simulate): +def test_simulate_free_functions(asgi, simulate, util): sink_called = [False] def sink(req, resp): sink_called[0] = True if asgi: - sink = to_coroutine(sink) + sink = util.to_coroutine(sink) - app = create_app(asgi) + app = util.create_app(asgi) app.add_sink(sink, '/test') simulate(app, '/test') @@ -737,7 +735,7 @@ def test_decode_empty_result(self, app): assert response.json == falcon.HTTPNotFound().to_dict() def test_httpnow_alias_for_backwards_compat(self): - assert testing.httpnow is util.http_now + assert testing.httpnow is falcon.util.http_now def test_default_headers(self, app): resource = testing.SimpleTestResource() @@ -801,7 +799,7 @@ def test_status(self, app): '\xe9\xe8', ), ) - def test_repr_result_when_body_varies(self, asgi, value, simulate): + def test_repr_result_when_body_varies(self, asgi, util, value, simulate): if isinstance(value, str): value = bytes(value, 'UTF-8') @@ -810,7 +808,7 @@ def test_repr_result_when_body_varies(self, asgi, value, simulate): else: resource = testing.SimpleTestResource(body=value) - app = create_app(asgi) + app = util.create_app(asgi) app.add_route('/hello', resource) result = simulate(app, '/hello') @@ -960,11 +958,11 @@ def test_query_string_in_path(self, app): }, ], ) - def test_simulate_json_body(self, asgi, document): + def test_simulate_json_body(self, asgi, util, document): resource = ( testing.SimpleTestResourceAsync() if asgi else testing.SimpleTestResource() ) - app = create_app(asgi) + app = util.create_app(asgi) app.add_route('/', resource) json_types = ('application/json', 'application/json; charset=UTF-8') @@ -1045,8 +1043,8 @@ def test_simulate_with_environ_extras(self, extras, expected_headers): for header, value in expected_headers: assert resource.captured_req.get_header(header) == value - def test_override_method_with_extras(self, asgi): - app = create_app(asgi) + def test_override_method_with_extras(self, asgi, util): + app = util.create_app(asgi) app.add_route('/', testing.SimpleTestResource(body='test')) client = testing.TestClient(app) @@ -1068,12 +1066,12 @@ def test_override_method_with_extras(self, asgi): 'application/yaml', ], ) - def test_simulate_content_type(self, content_type): + def test_simulate_content_type(self, util, content_type): class MediaMirror: def on_post(self, req, resp): resp.media = req.media - app = create_app(asgi=False) + app = util.create_app(asgi=False) app.add_route('/', MediaMirror()) client = testing.TestClient(app) @@ -1100,7 +1098,7 @@ def on_post(self, req, resp): ], ) @pytest.mark.skipif(msgpack is None, reason='msgpack is required for this test') - def test_simulate_content_type_extra_handler(self, asgi, content_type): + def test_simulate_content_type_extra_handler(self, asgi, util, content_type): class TestResourceAsync(testing.SimpleTestResourceAsync): def __init__(self): super().__init__() @@ -1122,7 +1120,7 @@ def on_post(self, req, resp): resp.content_type = content_type resource = TestResourceAsync() if asgi else TestResource() - app = create_app(asgi) + app = util.create_app(asgi) app.add_route('/', resource) json_handler = TrackingJSONHandler() @@ -1417,7 +1415,7 @@ def a_function(a=1, b=2): def test_json_deprecation(): with pytest.warns(deprecation.DeprecatedWarning, match='json'): - util.json + falcon.util.json with pytest.raises(AttributeError): - util.some_imaginary_module + falcon.util.some_imaginary_module diff --git a/tests/test_validators.py b/tests/test_validators.py index 6f04dd6a3..cb464fc66 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,17 +1,16 @@ import typing # NOQA: F401 -try: - import jsonschema -except ImportError: - jsonschema = None # type: ignore -from _util import create_app # NOQA -from _util import disable_asgi_non_coroutine_wrapping # NOQA import pytest import falcon from falcon import testing from falcon.media import validators +try: + import jsonschema +except ImportError: + jsonschema = None # type: ignore + _VALID_MEDIA = {'message': 'something'} _INVALID_MEDIA = {} # type: typing.Dict[str, str] @@ -150,7 +149,7 @@ def test_resp_schema_validation_failure(asgi): @skip_missing_dep -def test_both_schemas_validation_success(asgi): +def test_both_schemas_validation_success(asgi, util): req = MockReq(asgi) resp = MockResp() @@ -159,7 +158,7 @@ def test_both_schemas_validation_success(asgi): assert result[0] is req assert result[1] is resp - client = testing.TestClient(create_app(asgi)) + client = testing.TestClient(util.create_app(asgi)) resource = ResourceAsync() if asgi else Resource() client.app.add_route('/test', resource) @@ -168,7 +167,7 @@ def test_both_schemas_validation_success(asgi): @skip_missing_dep -def test_both_schemas_validation_failure(asgi): +def test_both_schemas_validation_failure(asgi, util): bad_resp = MockResp(False) with pytest.raises(falcon.HTTPInternalServerError) as excinfo: @@ -181,10 +180,10 @@ def test_both_schemas_validation_failure(asgi): assert excinfo.value.title == 'Request data failed validation' - client = testing.TestClient(create_app(asgi)) + client = testing.TestClient(util.create_app(asgi)) resource = ResourceAsync() if asgi else Resource() - with disable_asgi_non_coroutine_wrapping(): + with util.disable_asgi_non_coroutine_wrapping(): client.app.add_route('/test', resource) result = client.simulate_put('/test', json=_INVALID_MEDIA)