diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..62a85bc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# 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 Python + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + day: saturday + time: "07:00" diff --git a/dev-requirements.txt b/dev-requirements.txt index ac700ce..1586424 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,13 +1,13 @@ -r requirements.txt -black -marshmallow -pytest -pytest-cov -pytest-sugar -pytest-aiohttp -codecov -sphinx -sphinx_issues -sphinx_rtd_theme -isort -typed-ast +anyio~=4.3.0 +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 diff --git a/requirements.txt b/requirements.txt index 6d9eefb..90bdc5b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiohttp>=3.0.1,<4.0 -apispec>=5.1.1 -webargs>=8.0.1 -jinja2 +aiohttp>=3.9.4,<4.0 +apispec~=6.6.1 +webargs~=8.4.0 +jinja2~=3.1.3 diff --git a/tests/conftest.py b/tests/conftest.py index d40de32..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 ( @@ -16,6 +19,11 @@ ) +@pytest.fixture(scope="session") +def anyio_backend(): + return "asyncio" + + class HeaderSchema(Schema): class Meta: unknown = EXCLUDE @@ -65,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', @@ -85,7 +93,11 @@ def example_for_request_schema(): ({"location": "querystring"}, False), ] ) -def aiohttp_app(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( @@ -235,4 +247,5 @@ async def validated_view(request: web.Request): ) app.middlewares.extend([intercept_error, validation_middleware]) - return loop.run_until_complete(aiohttp_client(app)) + client = await aiohttp_client(app) + return client 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..2ac7061 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() == {} + 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")