From d61243935b17851a6d9e95b155188bd4e7ff98bc Mon Sep 17 00:00:00 2001 From: ff137 Date: Thu, 25 Apr 2024 12:47:13 +0200 Subject: [PATCH 01/11] :construction_worker: Add dependabot workflow --- .github/dependabot.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5a33db7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,20 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + + # Maintain dependencies for Python + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + day: saturday + time: "07:00" From e8c0b40723e05e82a679658ccf8ad9d199edacf7 Mon Sep 17 00:00:00 2001 From: Mourits de Beer <31511766+ff137@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:07:27 +0200 Subject: [PATCH 02/11] Update dependabot.yml --- .github/dependabot.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5a33db7..62a85bc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,12 +5,6 @@ version: 2 updates: - # Maintain dependencies for GitHub Actions - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - # Maintain dependencies for Python - package-ecosystem: pip directory: / From 80cda58e45df319b73d274790ff80ebae9a3e440 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 26 Apr 2024 15:20:20 +0300 Subject: [PATCH 03/11] :arrow_up: Bump minimum version of aiohttp for vulnerability fix --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6d9eefb..84aad84 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiohttp>=3.0.1,<4.0 +aiohttp>=3.9.4,<4.0 apispec>=5.1.1 webargs>=8.0.1 jinja2 From 3e444231de21dd848de9392e2d47abbabefa7d9a Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 26 Apr 2024 15:22:02 +0300 Subject: [PATCH 04/11] :arrow_up: Pin dependencies to latest versions --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 84aad84..90bdc5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ aiohttp>=3.9.4,<4.0 -apispec>=5.1.1 -webargs>=8.0.1 -jinja2 +apispec~=6.6.1 +webargs~=8.4.0 +jinja2~=3.1.3 From 2de1429629585b3222b89809a4ce5578f8ea3f81 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 26 Apr 2024 15:30:29 +0300 Subject: [PATCH 05/11] :pushpin: pin dev dependency versions --- dev-requirements.txt | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index ac700ce..dc6e7e2 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,13 +1,12 @@ -r requirements.txt -black -marshmallow -pytest -pytest-cov -pytest-sugar -pytest-aiohttp -codecov -sphinx -sphinx_issues -sphinx_rtd_theme -isort -typed-ast +black~=24.4.0 +codecov~=2.1.13 +isort~=5.13.2 +marshmallow~=3.21.1 +pytest~=8.1.1 +pytest-cov~=5.0.0 +pytest-sugar~=1.0.0 +pytest-aiohttp~=1.0.5 +sphinx~=7.3.7 +sphinx_issues~=4.1.0 +sphinx_rtd_theme~=2.0.0 From d906917f665e83d2609dc8fc82e308c5b2327729 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 26 Apr 2024 15:30:41 +0300 Subject: [PATCH 06/11] :heavy_plus_sign: add anyio --- dev-requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/dev-requirements.txt b/dev-requirements.txt index dc6e7e2..1586424 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,5 @@ -r requirements.txt +anyio~=4.3.0 black~=24.4.0 codecov~=2.1.13 isort~=5.13.2 From e1c67047885cec0aa79e84bb26a016e5bf2f79d8 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 26 Apr 2024 15:30:51 +0300 Subject: [PATCH 07/11] :art: define anyio backend --- tests/conftest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index d40de32..cc0bbc0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,6 +16,11 @@ ) +@pytest.fixture(scope="session") +def anyio_backend(): + return "asyncio" + + class HeaderSchema(Schema): class Meta: unknown = EXCLUDE From f66ba076d6d904dd49f3fa3bc1272b9f9670eb28 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 26 Apr 2024 15:38:48 +0300 Subject: [PATCH 08/11] event_loop instead of loop --- tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index cc0bbc0..d1ad68b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -90,7 +90,7 @@ def example_for_request_schema(): ({"location": "querystring"}, False), ] ) -def aiohttp_app(loop, aiohttp_client, request, example_for_request_schema): +def aiohttp_app(event_loop, aiohttp_client, request, example_for_request_schema): location, nested = request.param @docs( @@ -240,4 +240,4 @@ async def validated_view(request: web.Request): ) app.middlewares.extend([intercept_error, validation_middleware]) - return loop.run_until_complete(aiohttp_client(app)) + return event_loop.run_until_complete(aiohttp_client(app)) From 2bc289b1e456861c31b591308cbf52660b69221c Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 26 Apr 2024 16:14:04 +0300 Subject: [PATCH 09/11] :art: add typing and replace event_loop --- tests/conftest.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d1ad68b..87f8df6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,8 @@ +from typing import Any, Dict + import pytest from aiohttp import web +from aiohttp.test_utils import TestClient from marshmallow import EXCLUDE, INCLUDE, Schema, fields from aiohttp_apispec import ( @@ -70,7 +73,7 @@ def __init__(self, message): @pytest.fixture -def example_for_request_schema(): +def example_for_request_schema() -> Dict[str, Any]: return { 'id': 1, 'name': 'test', @@ -90,7 +93,11 @@ def example_for_request_schema(): ({"location": "querystring"}, False), ] ) -def aiohttp_app(event_loop, aiohttp_client, request, example_for_request_schema): +async def aiohttp_app( + aiohttp_client: TestClient, + request: pytest.FixtureRequest, + example_for_request_schema: Dict[str, Any], +): location, nested = request.param @docs( @@ -240,4 +247,5 @@ async def validated_view(request: web.Request): ) app.middlewares.extend([intercept_error, validation_middleware]) - return event_loop.run_until_complete(aiohttp_client(app)) + client = await aiohttp_client(app) + return client From 9ebe71cfc4e7c2b40a0b64f629a4f74f49655031 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 26 Apr 2024 16:14:39 +0300 Subject: [PATCH 10/11] :art: mark tests with @pytest.mark.anyio --- tests/test_documentation.py | 7 ++++++- tests/test_web_app.py | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/test_documentation.py b/tests/test_documentation.py index 3845782..2cdb02d 100644 --- a/tests/test_documentation.py +++ b/tests/test_documentation.py @@ -1,5 +1,6 @@ import json +import pytest from aiohttp import web from aiohttp.web_urldispatcher import StaticResource from yarl import URL @@ -7,7 +8,8 @@ from aiohttp_apispec import setup_aiohttp_apispec -def test_app_swagger_url(aiohttp_app): +@pytest.mark.anyio +async def test_app_swagger_url(aiohttp_app): def safe_url_for(route): if isinstance(route._resource, StaticResource): # url_for on StaticResource requires filename arg @@ -21,6 +23,7 @@ def safe_url_for(route): assert URL("/v1/api/docs/api-docs") in urls +@pytest.mark.anyio async def test_app_swagger_json(aiohttp_app, example_for_request_schema): resp = await aiohttp_app.get("/v1/api/docs/api-docs") docs = await resp.json() @@ -174,6 +177,7 @@ async def test_app_swagger_json(aiohttp_app, example_for_request_schema): ) +@pytest.mark.anyio async def test_not_register_route_for_none_url(): app = web.Application() routes_count = len(app.router.routes()) @@ -182,6 +186,7 @@ async def test_not_register_route_for_none_url(): assert routes_count == routes_count_after_setup_apispec +@pytest.mark.anyio async def test_register_route_for_relative_url(): app = web.Application() routes_count = len(app.router.routes()) diff --git a/tests/test_web_app.py b/tests/test_web_app.py index 1c960c6..f7301de 100644 --- a/tests/test_web_app.py +++ b/tests/test_web_app.py @@ -1,8 +1,13 @@ +import pytest + + +@pytest.mark.anyio async def test_response_200_get(aiohttp_app): res = await aiohttp_app.get("/v1/test", params={"id": 1, "name": "max"}) assert res.status == 200 +@pytest.mark.anyio async def test_response_400_get(aiohttp_app): res = await aiohttp_app.get("/v1/test", params={"id": "string", "name": "max"}) assert res.status == 400 @@ -12,16 +17,19 @@ async def test_response_400_get(aiohttp_app): } +@pytest.mark.anyio async def test_response_200_post(aiohttp_app): res = await aiohttp_app.post("/v1/test", json={"id": 1, "name": "max"}) assert res.status == 200 +@pytest.mark.anyio async def test_response_200_post_callable_schema(aiohttp_app): res = await aiohttp_app.post("/v1/test_call", json={"id": 1, "name": "max"}) assert res.status == 200 +@pytest.mark.anyio async def test_response_400_post(aiohttp_app): res = await aiohttp_app.post("/v1/test", json={"id": "string", "name": "max"}) assert res.status == 400 @@ -31,6 +39,7 @@ async def test_response_400_post(aiohttp_app): } +@pytest.mark.anyio async def test_response_400_post_unknown_toplevel_field(aiohttp_app): # unknown_field is not a field in RequestSchema, default behavior is RAISE exception res = await aiohttp_app.post( @@ -43,6 +52,7 @@ async def test_response_400_post_unknown_toplevel_field(aiohttp_app): } +@pytest.mark.anyio async def test_response_400_post_nested_fields(aiohttp_app): payload = { 'nested_field': { @@ -58,11 +68,13 @@ async def test_response_400_post_nested_fields(aiohttp_app): } +@pytest.mark.anyio async def test_response_not_docked(aiohttp_app): res = await aiohttp_app.get("/v1/other", params={"id": 1, "name": "max"}) assert res.status == 200 +@pytest.mark.anyio async def test_response_data_post(aiohttp_app): res = await aiohttp_app.post( "/v1/echo", json={"id": 1, "name": "max", "list_field": [1, 2, 3, 4]} @@ -70,6 +82,7 @@ async def test_response_data_post(aiohttp_app): assert (await res.json()) == {"id": 1, "name": "max", "list_field": [1, 2, 3, 4]} +@pytest.mark.anyio async def test_response_data_get(aiohttp_app): res = await aiohttp_app.get( "/v1/echo", @@ -91,6 +104,7 @@ async def test_response_data_get(aiohttp_app): } +@pytest.mark.anyio async def test_response_data_class_get(aiohttp_app): res = await aiohttp_app.get( "/v1/class_echo", @@ -112,11 +126,13 @@ async def test_response_data_class_get(aiohttp_app): } +@pytest.mark.anyio async def test_response_data_class_post(aiohttp_app): res = await aiohttp_app.post("/v1/class_echo") assert res.status == 405 +@pytest.mark.anyio async def test_path_variable_described_correctly(aiohttp_app): if aiohttp_app.app._subapps: swag = aiohttp_app.app._subapps[0]["swagger_dict"]["paths"][ @@ -129,22 +145,26 @@ async def test_path_variable_described_correctly(aiohttp_app): assert swag["get"]["parameters"][0]["schema"]["format"] == "uuid" +@pytest.mark.anyio async def test_response_data_class_without_spec(aiohttp_app): res = await aiohttp_app.delete("/v1/class_echo") assert (await res.json()) == {"hello": "world"} +@pytest.mark.anyio async def test_swagger_handler_200(aiohttp_app): res = await aiohttp_app.get("/v1/api/docs/api-docs") assert res.status == 200 +@pytest.mark.anyio async def test_match_info(aiohttp_app): res = await aiohttp_app.get("/v1/variable/hello") assert res.status == 200 assert await res.json() == {} +@pytest.mark.anyio async def test_validators(aiohttp_app): res = await aiohttp_app.post( "/v1/validate/123456", @@ -181,11 +201,13 @@ async def test_validators(aiohttp_app): } +@pytest.mark.anyio async def test_swagger_path(aiohttp_app): res = await aiohttp_app.get("/v1/api/docs") assert res.status == 200 +@pytest.mark.anyio async def test_swagger_static(aiohttp_app): assert (await aiohttp_app.get("/static/swagger/swagger-ui.css")).status == 200 or ( await aiohttp_app.get("/v1/static/swagger/swagger-ui.css") From c2d52472fbee5b4bdbda46a112db78586a340ab7 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 26 Apr 2024 16:15:01 +0300 Subject: [PATCH 11/11] :white_check_mark: fix test returning [] instead of {} ... :-? --- tests/test_web_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_web_app.py b/tests/test_web_app.py index f7301de..2ac7061 100644 --- a/tests/test_web_app.py +++ b/tests/test_web_app.py @@ -161,7 +161,7 @@ async def test_swagger_handler_200(aiohttp_app): async def test_match_info(aiohttp_app): res = await aiohttp_app.get("/v1/variable/hello") assert res.status == 200 - assert await res.json() == {} + assert await res.json() == [] @pytest.mark.anyio