diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index fe515aa..9e9d59e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.7 - name: Pip cache uses: actions/cache@v1 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index edaf3bb..9055f35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} @@ -51,7 +51,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.7 - name: Pip cache uses: actions/cache@v1 with: diff --git a/README.md b/README.md index 44154ca..f68efd8 100644 --- a/README.md +++ b/README.md @@ -68,5 +68,5 @@ Install with pip: $ pip install respx ``` -Requires Python 3.6+ and HTTPX 0.21+. +Requires Python 3.7+ and HTTPX 0.21+. See [Changelog](https://github.com/lundberg/respx/blob/master/CHANGELOG.md) for older HTTPX compatibility. diff --git a/docs/examples.md b/docs/examples.md index 800f0b1..6deb1de 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -97,7 +97,6 @@ import respx @respx.mock -@pytest.mark.asyncio async def test_async_decorator(): async with httpx.AsyncClient() as client: route = respx.get("https://example.org/") @@ -106,7 +105,6 @@ async def test_async_decorator(): assert response.status_code == 200 -@pytest.mark.asyncio async def test_async_ctx_manager(): async with respx.mock: async with httpx.AsyncClient() as client: diff --git a/docs/index.md b/docs/index.md index ad870cb..2572564 100644 --- a/docs/index.md +++ b/docs/index.md @@ -68,5 +68,5 @@ Install with pip: $ pip install respx ``` -Requires Python 3.6+ and HTTPX 0.21+. +Requires Python 3.7+ and HTTPX 0.21+. See [Changelog](https://github.com/lundberg/respx/blob/master/CHANGELOG.md) for older HTTPX compatibility. diff --git a/docs/versions/0.14.0/mocking.md b/docs/versions/0.14.0/mocking.md index 7f025ba..920b668 100644 --- a/docs/versions/0.14.0/mocking.md +++ b/docs/versions/0.14.0/mocking.md @@ -250,4 +250,3 @@ class MyTestCase(asynctest.TestCase): assert request.called assert response.text == "foobar" ``` - diff --git a/noxfile.py b/noxfile.py index 35adc7e..4041dc1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -9,7 +9,7 @@ docs_requirements = ("mkdocs", "mkdocs-material", "mkautodoc>=0.1.0") -@nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"]) +@nox.session(python=["3.7", "3.8", "3.9", "3.10", "3.11"]) def test(session): deps = ["pytest", "pytest-asyncio", "pytest-cov", "trio", "starlette", "flask"] session.install("--upgrade", *deps) @@ -22,12 +22,12 @@ def test(session): session.run("pytest", "-v", *options) -@nox.session(python="3.6") +@nox.session(python="3.7") def check(session): session.install("--upgrade", "flake8-bugbear", "mypy", *lint_requirements) session.install("-e", ".") - session.run("black", "--check", "--diff", "--target-version=py36", *source_files) + session.run("black", "--check", "--diff", "--target-version=py37", *source_files) session.run("isort", "--check", "--diff", "--project=respx", *source_files) session.run("flake8", *source_files) session.run("mypy") @@ -39,7 +39,7 @@ def lint(session): session.run("autoflake", "--in-place", "--recursive", *source_files) session.run("isort", "--project=respx", *source_files) - session.run("black", "--target-version=py36", *source_files) + session.run("black", "--target-version=py37", *source_files) session.notify("check") diff --git a/respx/handlers.py b/respx/handlers.py index 0d4ad40..f383678 100644 --- a/respx/handlers.py +++ b/respx/handlers.py @@ -9,7 +9,7 @@ def __init__(self, transport: httpx.BaseTransport) -> None: def __call__(self, request: httpx.Request) -> httpx.Response: if not isinstance( - request.stream, # type: ignore[has-type] + request.stream, httpx.SyncByteStream, ): # pragma: nocover raise RuntimeError("Attempted to route an async request to a sync app.") @@ -23,7 +23,7 @@ def __init__(self, transport: httpx.AsyncBaseTransport) -> None: async def __call__(self, request: httpx.Request) -> httpx.Response: if not isinstance( - request.stream, # type: ignore[has-type] + request.stream, httpx.AsyncByteStream, ): # pragma: nocover raise RuntimeError("Attempted to route a sync request to an async app.") diff --git a/respx/models.py b/respx/models.py index cb38b17..9200817 100644 --- a/respx/models.py +++ b/respx/models.py @@ -35,7 +35,7 @@ def clone_response(response: httpx.Response, request: httpx.Request) -> httpx.Re response = httpx.Response( response.status_code, headers=response.headers, - stream=response.stream, # type: ignore[has-type] + stream=response.stream, request=request, extensions=dict(response.extensions), ) diff --git a/respx/router.py b/respx/router.py index b42926c..1d93d70 100644 --- a/respx/router.py +++ b/respx/router.py @@ -283,9 +283,7 @@ def resolve(self, request: httpx.Request) -> ResolvedRoute: resolved.response = cast(ResolvedResponseTypes, prospect) break - if resolved.response and isinstance( - resolved.response.stream, httpx.ByteStream # type: ignore[has-type] - ): + if resolved.response and isinstance(resolved.response.stream, httpx.ByteStream): resolved.response.read() # Pre-read stream return resolved @@ -307,9 +305,7 @@ async def aresolve(self, request: httpx.Request) -> ResolvedRoute: resolved.response = cast(ResolvedResponseTypes, prospect) break - if resolved.response and isinstance( - resolved.response.stream, httpx.ByteStream # type: ignore[has-type] - ): + if resolved.response and isinstance(resolved.response.stream, httpx.ByteStream): await resolved.response.aread() # Pre-read stream return resolved diff --git a/setup.cfg b/setup.cfg index 1838f0c..a2bb004 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,7 +3,7 @@ universal = 1 [flake8] max-line-length = 88 -ignore = E501,E266,E731,W503,E203,B024 +ignore = B024,C408,E203,W503 exclude = .git show-source = true @@ -36,7 +36,7 @@ skip_covered = True show_missing = True [mypy] -python_version = 3.6 +python_version = 3.7 files = respx,tests pretty = True diff --git a/setup.py b/setup.py index 04a9f0d..18963ea 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,6 @@ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", @@ -39,6 +38,6 @@ entry_points={"pytest11": ["respx = respx.plugin"]}, include_package_data=True, zip_safe=False, - python_requires=">=3.6", + python_requires=">=3.7", install_requires=["httpx>=0.21.0"], ) diff --git a/tests/test_api.py b/tests/test_api.py index 8b019d8..4a91442 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -14,7 +14,6 @@ from respx.router import MockRouter -@pytest.mark.asyncio async def test_http_methods(client): async with respx.mock: url = "https://foo.bar" @@ -73,7 +72,6 @@ async def test_http_methods(client): assert respx.calls.call_count == 8 * 2 -@pytest.mark.asyncio @pytest.mark.parametrize( "url,pattern", [ @@ -95,14 +93,12 @@ async def test_url_match(client, url, pattern): assert response.text == "baz" -@pytest.mark.asyncio async def test_invalid_url_pattern(): async with MockRouter() as respx_mock: with pytest.raises(TypeError): respx_mock.get(["invalid"]) # type: ignore[arg-type] -@pytest.mark.asyncio async def test_repeated_pattern(client): async with MockRouter() as respx_mock: url = "https://foo/bar/baz/" @@ -127,7 +123,6 @@ async def test_repeated_pattern(client): assert statuses == [201, 409] -@pytest.mark.asyncio async def test_status_code(client): async with MockRouter() as respx_mock: url = "https://foo.bar/" @@ -138,7 +133,6 @@ async def test_status_code(client): assert response.status_code == 404 -@pytest.mark.asyncio @pytest.mark.parametrize( "headers,content_type,expected", [ @@ -166,7 +160,6 @@ async def test_headers(client, headers, content_type, expected): assert response.headers == httpx.Headers(expected) -@pytest.mark.asyncio @pytest.mark.parametrize( "content,expected", [ @@ -184,7 +177,6 @@ async def test_text_encoding(client, content, expected): assert response.text == expected -@pytest.mark.asyncio @pytest.mark.parametrize( "key,value,expected_content_type", [ @@ -214,7 +206,6 @@ async def test_content_variants(client, key, value, expected_content_type): assert sync_response.content is not None -@pytest.mark.asyncio @pytest.mark.parametrize( "content,headers,expected_headers", [ @@ -272,15 +263,11 @@ def test_json_post_body(): assert get_route.called -@pytest.mark.asyncio async def test_raising_content(client): async with MockRouter() as respx_mock: url = "https://foo.bar/" request = respx_mock.get(url) - request.side_effect = httpx.ConnectTimeout( - "X-P", - request=None, # type: ignore[arg-type] - ) + request.side_effect = httpx.ConnectTimeout("X-P", request=None) with pytest.raises(httpx.ConnectTimeout): await client.get(url) @@ -301,7 +288,6 @@ async def test_raising_content(client): assert route.calls.last.response -@pytest.mark.asyncio async def test_callable_content(client): async with MockRouter() as respx_mock: url_pattern = re.compile(r"https://foo.bar/(?P\w+)/") @@ -327,7 +313,6 @@ def content_callback(request, slug): assert request.calls[-1][0].content == b'{"x": "!"}' -@pytest.mark.asyncio async def test_request_callback(client): def callback(request, name): if request.url.host == "foo.bar" and request.content == b'{"foo": "bar"}': @@ -375,7 +360,6 @@ def _callback(request): await client.get("https://egg.plant/") -@pytest.mark.asyncio @pytest.mark.parametrize( "using,route,expected", [ @@ -415,7 +399,6 @@ async def test_pass_through(client, using, route, expected): @respx.mock -@pytest.mark.asyncio async def test_parallel_requests(client): def content(request, page): return httpx.Response(200, text=page) @@ -433,7 +416,6 @@ def content(request, page): assert respx.calls.call_count == 2 -@pytest.mark.asyncio @pytest.mark.parametrize( "method_str, client_method_attr", [ @@ -476,7 +458,6 @@ def test_pop(): @respx.mock -@pytest.mark.asyncio @pytest.mark.parametrize( "url,params,call_url,call_params", [ @@ -503,7 +484,6 @@ async def test_params_match(client, url, params, call_url, call_params): assert response.text == "spam spam" -@pytest.mark.asyncio @pytest.mark.parametrize( "base,url", [ @@ -564,7 +544,6 @@ def test_respond(): route.respond(content=Exception()) # type: ignore[arg-type] -@pytest.mark.asyncio @pytest.mark.parametrize( "kwargs", [ diff --git a/tests/test_mock.py b/tests/test_mock.py index ddb85fc..5cb2b7b 100644 --- a/tests/test_mock.py +++ b/tests/test_mock.py @@ -11,7 +11,6 @@ from respx.router import MockRouter -@pytest.mark.asyncio @respx.mock async def test_decorating_test(client): assert respx.calls.call_count == 0 @@ -26,7 +25,6 @@ async def test_decorating_test(client): respx.routes["home"].calls.assert_called_once() -@pytest.mark.asyncio async def test_mock_request_fixture(client, my_mock): assert respx.calls.call_count == 0 assert my_mock.calls.call_count == 0 @@ -39,7 +37,6 @@ async def test_mock_request_fixture(client, my_mock): assert my_mock.calls.call_count == 1 -@pytest.mark.asyncio async def test_mock_single_session_fixture(client, mocked_foo): current_foo_call_count = mocked_foo.calls.call_count response = await client.get("https://foo.api/api/bar/") @@ -49,7 +46,6 @@ async def test_mock_single_session_fixture(client, mocked_foo): assert mocked_foo.calls.call_count == current_foo_call_count + 1 -@pytest.mark.asyncio async def test_mock_multiple_session_fixtures(client, mocked_foo, mocked_ham): current_foo_call_count = mocked_foo.calls.call_count current_ham_call_count = mocked_ham.calls.call_count @@ -83,7 +79,6 @@ def test(): assert respx.calls.call_count == 0 -@pytest.mark.asyncio async def test_global_async_decorator(client): @respx.mock async def test(): @@ -119,7 +114,6 @@ def test(respx_mock): assert respx.calls.call_count == 0 -@pytest.mark.asyncio @pytest.mark.parametrize("using", ["httpcore", "httpx"]) async def test_local_async_decorator(client, using): @respx.mock(using=using) @@ -175,7 +169,6 @@ def test(): assert respx.calls.call_count == 0 -@pytest.mark.asyncio async def test_global_contextmanager(client): with respx.mock: assert respx.calls.call_count == 0 @@ -196,7 +189,6 @@ async def test_global_contextmanager(client): assert respx.calls.call_count == 0 -@pytest.mark.asyncio async def test_local_contextmanager(client): with respx.mock() as respx_mock: assert respx_mock.calls.call_count == 0 @@ -219,7 +211,6 @@ async def test_local_contextmanager(client): assert respx.calls.call_count == 0 -@pytest.mark.asyncio async def test_nested_local_contextmanager(client): with respx.mock() as respx_mock_1: get_request = respx_mock_1.get("https://foo/bar/") % 202 @@ -246,7 +237,6 @@ async def test_nested_local_contextmanager(client): assert len(respx.routes) == 0 -@pytest.mark.asyncio async def test_nested_global_contextmanager(client): with respx.mock: get_request = respx.get("https://foo/bar/") % 202 @@ -271,7 +261,6 @@ async def test_nested_global_contextmanager(client): assert len(respx.routes) == 0 -@pytest.mark.asyncio async def test_configured_decorator(client): @respx.mock(assert_all_called=False, assert_all_mocked=False) async def test(respx_mock): @@ -298,7 +287,6 @@ async def test(respx_mock): assert respx.calls.call_count == 0 -@pytest.mark.asyncio @respx.mock(base_url="https://foo.bar") async def test_configured_decorator_with_fixture(respx_mock, client): respx_mock.get("/") @@ -306,7 +294,6 @@ async def test_configured_decorator_with_fixture(respx_mock, client): assert response.status_code == 200 -@pytest.mark.asyncio async def test_configured_router_reuse(client): router = respx.mock() route = router.get("https://foo/bar/") % 404 @@ -340,7 +327,6 @@ async def test_configured_router_reuse(client): assert respx.calls.call_count == 0 -@pytest.mark.asyncio async def test_router_return_type_misuse(): router = respx.mock(assert_all_called=False) route = router.get("https://hot.dog/") @@ -349,7 +335,6 @@ async def test_router_return_type_misuse(): route.return_value = "not-a-httpx-response" # type: ignore[assignment] -@pytest.mark.asyncio @respx.mock(base_url="https://ham.spam/") async def test_nested_base_url(respx_mock): request = respx_mock.patch("/egg/") % dict(content="yolk") @@ -388,7 +373,6 @@ def test_leakage(mocked_foo, mocked_ham): assert len(Mocker.registry["httpcore"].routers) == 2 -@pytest.mark.asyncio async def test_start_stop(client): url = "https://start.stop/" request = respx.get(url) % 202 @@ -418,7 +402,6 @@ async def test_start_stop(client): respx.stop() # Cleanup global state on error, to not affect other tests -@pytest.mark.asyncio @pytest.mark.parametrize( "assert_all_called,do_post,raises", [ @@ -442,7 +425,6 @@ async def test_assert_all_called(client, assert_all_called, do_post, raises): assert request2.called is do_post -@pytest.mark.asyncio @pytest.mark.parametrize( "assert_all_mocked,raises", [(True, pytest.raises(AllMockedAssertionError)), (False, does_not_raise())], @@ -487,7 +469,6 @@ def test_add_remove_targets(): assert len(HTTPCoreMocker.targets) == pre_add_count -@pytest.mark.asyncio async def test_proxies(): with respx.mock: respx.get("https://foo.bar/") % dict(json={"foo": "bar"}) @@ -504,7 +485,6 @@ async def test_proxies(): assert response.json() == {"foo": "bar"} -@pytest.mark.asyncio async def test_uds(): async with respx.mock: uds = httpx.AsyncHTTPTransport(uds="/tmp/foobar.sock") @@ -515,7 +495,6 @@ async def test_uds(): assert response.status_code == 202 -@pytest.mark.asyncio async def test_mock_using_none(): @respx.mock(using=None) async def test(respx_mock): @@ -528,7 +507,6 @@ async def test(respx_mock): await test() -@pytest.mark.asyncio async def test_router_using__none(): router = respx.MockRouter(using=None) router.get("https://example.org/") % 204 @@ -588,7 +566,6 @@ def test(respx_mock): test() -@pytest.mark.asyncio async def test_async_httpx_mocker(): class TestTransport(httpx.AsyncBaseTransport): async def handle_async_request(self, *args, **kwargs): @@ -629,7 +606,6 @@ async def content(): await test() -@pytest.mark.asyncio @pytest.mark.parametrize("using", ["httpcore", "httpx"]) async def test_async_side_effect(client, using): async def effect(request, slug): @@ -646,7 +622,6 @@ async def effect(request, slug): assert mock_route.called -@pytest.mark.asyncio @pytest.mark.parametrize("using", ["httpcore", "httpx"]) async def test_async_side_effect__exception(client, using): async def effect(request): @@ -659,7 +634,6 @@ async def effect(request): assert mock_route.called -@pytest.mark.asyncio @pytest.mark.parametrize("using", ["httpcore", "httpx"]) async def test_async_app_route(client, using): from starlette.applications import Starlette @@ -705,7 +679,6 @@ def baz(): assert response.json() == {"ham": "spam"} -@pytest.mark.asyncio @pytest.mark.parametrize( "url,port", [ @@ -733,7 +706,6 @@ async def test_httpcore_request(url, port): assert body == b"foobar" -@pytest.mark.asyncio async def test_route_rollback(): respx_mock = respx.mock() diff --git a/tests/test_router.py b/tests/test_router.py index f7549c0..cb6ad01 100644 --- a/tests/test_router.py +++ b/tests/test_router.py @@ -9,7 +9,6 @@ from respx.patterns import Host, M, Method -@pytest.mark.asyncio async def test_empty_router(): router = Router() @@ -21,7 +20,6 @@ async def test_empty_router(): await router.aresolve(request) -@pytest.mark.asyncio async def test_empty_router__auto_mocked(): router = Router(assert_all_mocked=False) @@ -178,7 +176,6 @@ def test_mod_response(): router.route() % [] # type: ignore[operator] -@pytest.mark.asyncio async def test_async_side_effect(): router = Router() diff --git a/tests/test_stats.py b/tests/test_stats.py index 68ef6d4..9cf8099 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -7,7 +7,6 @@ from respx.router import MockRouter -@pytest.mark.asyncio async def test_named_route(): async with MockRouter(assert_all_called=False) as respx_mock: request = respx_mock.get("https://foo.bar/", name="foobar") diff --git a/tests/test_transports.py b/tests/test_transports.py index 5caf155..4be1223 100644 --- a/tests/test_transports.py +++ b/tests/test_transports.py @@ -27,7 +27,6 @@ def test_sync_transport_handler(): client.post(url) -@pytest.mark.asyncio async def test_async_transport_handler(): url = "https://foo.bar/" @@ -47,7 +46,6 @@ async def test_async_transport_handler(): await client.post(url) -@pytest.mark.asyncio async def test_transport_assertions(): url = "https://foo.bar/"