From 44451e0a4e280255f7927499c8b4068f03928c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20K=C3=B6tter?= Date: Thu, 3 Oct 2024 14:25:10 +0200 Subject: [PATCH] pytest-asyncio 0.24 event loop scope --- pyproject.toml | 3 ++- tests/apiv1_test.py | 33 +++++++++++++++------------ tests/apiv2_test.py | 54 ++++++++++++++++++++++++-------------------- tests/conftest.py | 8 ------- tests/forms_test.py | 47 +++++++++++++++++++------------------- tests/stream_test.py | 33 ++++++++++++++------------- tests/tls_test.py | 31 +++++++++++++------------ 7 files changed, 105 insertions(+), 104 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2090e057..eccfdc1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,7 +85,8 @@ filterwarnings = [ "ignore:unclosed 0 -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") @pytest.mark.skipif(sys.version_info < (3, 9), reason="requires asyncio.to_thread") -async def test_getPet(event_loop, server, client): +async def test_getPet(server, client): h, pet = await asyncio.to_thread(client._.createPet, **randomPet(uuid.uuid4())) r = await asyncio.to_thread(client._.getPet, parameters={"petId": pet.id}) # FastAPI 0.101 Serialization changes @@ -79,9 +82,9 @@ async def test_getPet(event_loop, server, client): assert type(r).model_json_schema() == client.components.schemas["Error"].get_type().model_json_schema() -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") @pytest.mark.skipif(sys.version_info < (3, 9), reason="requires asyncio.to_thread") -async def test_deletePet(event_loop, server, client): +async def test_deletePet(server, client): r = await asyncio.to_thread(client._.deletePet, parameters={"petId": -1}) print(r) assert type(r).model_json_schema() == client.components.schemas["Error"].get_type().model_json_schema() diff --git a/tests/apiv2_test.py b/tests/apiv2_test.py index 0f29bcbc..3189c3c7 100644 --- a/tests/apiv2_test.py +++ b/tests/apiv2_test.py @@ -41,10 +41,14 @@ def config(unused_tcp_port_factory): return c -@pytest_asyncio.fixture(scope="session") -async def server(event_loop, config): - policy = asyncio.get_event_loop_policy() - asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) +@pytest.fixture(scope="session") +def event_loop_policy(): + return uvloop.EventLoopPolicy() + + +@pytest_asyncio.fixture(loop_scope="session") +async def server(config): + event_loop = asyncio.get_running_loop() try: sd = asyncio.Event() task = event_loop.create_task(serve(app, config, shutdown_trigger=sd.wait)) @@ -52,7 +56,6 @@ async def server(event_loop, config): finally: sd.set() await task - asyncio.set_event_loop_policy(policy) @pytest.fixture(scope="session", params=[2]) @@ -63,14 +66,15 @@ def version(request): from aiopenapi3.debug import DescriptionDocumentDumper -@pytest_asyncio.fixture(scope="session") -async def client(event_loop, server, version): +@pytest_asyncio.fixture(loop_scope="session") +async def client(server, version): url = f"http://{server.bind[0]}/{version}/openapi.json" api = await aiopenapi3.OpenAPI.load_async(url, plugins=[DescriptionDocumentDumper("/tmp/schema.yaml")]) return api +@pytest.mark.asyncio(loop_scope="session") @pytest.mark.xfail() def test_Pet(): import json @@ -84,24 +88,24 @@ def test_Pet(): assert t.model_json_schema() == data -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") @pytest.mark.skipif(sys.version_info < (3, 9), reason="requires asyncio.to_thread") -async def test_sync(event_loop, server, version): +async def test_sync(server, version): url = f"http://{server.bind[0]}/{version}/openapi.json" api = await asyncio.to_thread(aiopenapi3.OpenAPI.load_sync, url) return api -@pytest.mark.asyncio -async def test_description_document(event_loop, server, version): +@pytest.mark.asyncio(loop_scope="session") +async def test_description_document(server, version): url = f"http://{server.bind[0]}/{version}/openapi.json" api = await aiopenapi3.OpenAPI.load_async(url) return api @pytest.mark.xfail() -@pytest.mark.asyncio -async def test_model(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_model(server, client): orig = client.components.schemas["WhiteCat"].model_dump(exclude_unset=True) crea = client.components.schemas["WhiteCat"].get_type().model_json_schema() assert orig == crea @@ -150,16 +154,16 @@ def randomPet(client, name=None, cat=False): } -@pytest.mark.asyncio -async def test_Request(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_Request(server, client): client._.createPet.data client._.createPet.parameters client._.createPet.args() client._.createPet.return_value() -@pytest.mark.asyncio -async def test_createPet(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_createPet(server, client): data = { "pet": client.components.schemas["WhiteCat"] .model( @@ -188,8 +192,8 @@ async def test_createPet(event_loop, server, client): cls() -@pytest.mark.asyncio -async def test_listPet(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_listPet(server, client): r = await client._.createPet(data=randomPet(client, str(uuid.uuid4()))) l = await client._.listPet(parameters={"limit": 1}) assert len(l) > 0 @@ -198,8 +202,8 @@ async def test_listPet(event_loop, server, client): assert isinstance(l, client.components.schemas["HTTPValidationError"].get_type()) -@pytest.mark.asyncio -async def test_getPet(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_getPet(server, client): pet = await client._.createPet(data=randomPet(client, str(uuid.uuid4()))) r = await client._.getPet(parameters={"petId": pet.identifier}) @@ -211,8 +215,8 @@ async def test_getPet(event_loop, server, client): assert type(r).model_json_schema() == client.components.schemas["Error"].get_type().model_json_schema() -@pytest.mark.asyncio -async def test_deletePet(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_deletePet(server, client): r = await client._.deletePet(parameters={"petId": uuid.uuid4(), "x-raise-nonexist": False}) assert type(r).model_json_schema() == client.components.schemas["Error"].get_type().model_json_schema() @@ -224,8 +228,8 @@ async def test_deletePet(event_loop, server, client): await client._.deletePet(parameters={"petId": pet.identifier, "x-raise-nonexist": None}) -@pytest.mark.asyncio -async def test_patchPet(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_patchPet(server, client): Pet = client.components.schemas["Pet-Input"].get_type() Dog = typing.get_args(typing.get_args(Pet.model_fields["root"].annotation)[0])[1] pets = [ diff --git a/tests/conftest.py b/tests/conftest.py index f882ada3..7acded85 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -import asyncio import os import dataclasses @@ -13,13 +12,6 @@ URLBASE = "/" -@pytest.fixture(scope="session") -def event_loop(request): - loop = asyncio.get_event_loop_policy().new_event_loop() - yield loop - loop.close() - - @pytest.fixture(autouse=True) def skip_env(request): if request.node.get_closest_marker("skip_env"): diff --git a/tests/forms_test.py b/tests/forms_test.py index 66e4edda..9d2948db 100644 --- a/tests/forms_test.py +++ b/tests/forms_test.py @@ -269,9 +269,9 @@ def config(unused_tcp_port_factory): return c -@pytest_asyncio.fixture(scope="session") -async def server(event_loop, config, app): - policy = asyncio.get_event_loop_policy() +@pytest_asyncio.fixture(loop_scope="session") +async def server(config, app): + event_loop = asyncio.get_event_loop() try: sd = asyncio.Event() asgi = WsgiToAsgi(app) @@ -281,7 +281,6 @@ async def server(event_loop, config, app): sd.set() del asgi await task - asyncio.set_event_loop_policy(policy) @pytest.fixture(scope="session", params=["application/x-www-form-urlencoded", "multipart/form-data"]) @@ -289,7 +288,7 @@ def form_type(request): return f"{request.param}" -@pytest_asyncio.fixture(scope="session") +@pytest_asyncio.fixture(loop_scope="session") async def client(server, form_type, with_paths_requestbody_formdata_wtforms): data = copy.deepcopy(with_paths_requestbody_formdata_wtforms) if form_type != "multipart/form-data": @@ -303,13 +302,13 @@ async def client(server, form_type, with_paths_requestbody_formdata_wtforms): @pytest.mark.asyncio -async def _test_service(event_loop, server, client): +async def _test_service(server, client): while True: await asyncio.sleep(1) -@pytest.mark.asyncio -async def test_Test(event_loop, server, client, form_type): +@pytest.mark.asyncio(loop_scope="session") +async def test_Test(server, client, form_type): cls = client._.test.operation.requestBody.content[form_type].schema_.get_type() data = cls(string="yes", number="5", file="test") @@ -317,8 +316,8 @@ async def test_Test(event_loop, server, client, form_type): assert r == "ok" -@pytest.mark.asyncio -async def test_String(event_loop, server, client, form_type): +@pytest.mark.asyncio(loop_scope="session") +async def test_String(server, client, form_type): cls = client._.string.operation.requestBody.content[form_type].schema_.get_type() data = cls( string="yes", @@ -334,8 +333,8 @@ async def test_String(event_loop, server, client, form_type): assert r == "ok" -@pytest.mark.asyncio -async def test_DateTime(event_loop, server, client, form_type): +@pytest.mark.asyncio(loop_scope="session") +async def test_DateTime(server, client, form_type): cls = client._.datetime.operation.requestBody.content[form_type].schema_.get_type() now = datetime.datetime.now() @@ -347,8 +346,8 @@ async def test_DateTime(event_loop, server, client, form_type): assert r == "ok" -@pytest.mark.asyncio -async def test_Numbers(event_loop, server, client, form_type): +@pytest.mark.asyncio(loop_scope="session") +async def test_Numbers(server, client, form_type): cls = client._.numbers.operation.requestBody.content[form_type].schema_.get_type() data = cls( @@ -364,8 +363,8 @@ async def test_Numbers(event_loop, server, client, form_type): assert r == "ok" -@pytest.mark.asyncio -async def test_File(event_loop, server, client, form_type): +@pytest.mark.asyncio(loop_scope="session") +async def test_File(server, client, form_type): cls = client._.file.operation.requestBody.content[form_type].schema_.get_type() data = cls(file=b"4711", files=[b"a", b"b"], xml=b"yes") @@ -374,8 +373,8 @@ async def test_File(event_loop, server, client, form_type): assert r == "ok" -@pytest.mark.asyncio -async def test_Select(event_loop, server, client, form_type): +@pytest.mark.asyncio(loop_scope="session") +async def test_Select(server, client, form_type): cls = client._.select.operation.requestBody.content[form_type].schema_.get_type() data = cls(radio="py", select="rb", selectmultiple=["c", "cpp"]) @@ -384,8 +383,8 @@ async def test_Select(event_loop, server, client, form_type): assert r == "ok" -@pytest.mark.asyncio -async def test_Control(event_loop, server, client, form_type): +@pytest.mark.asyncio(loop_scope="session") +async def test_Control(server, client, form_type): cls = client._.control.operation.requestBody.content[form_type].schema_.get_type() data = cls(submit="yes", search="no") @@ -394,8 +393,8 @@ async def test_Control(event_loop, server, client, form_type): assert r == "ok" -@pytest.mark.asyncio -async def test_Header(event_loop, server, client, form_type): +@pytest.mark.asyncio(loop_scope="session") +async def test_Header(server, client, form_type): if form_type != "multipart/form-data": pytest.skip() @@ -409,8 +408,8 @@ async def test_Header(event_loop, server, client, form_type): assert r == "ok" -@pytest.mark.asyncio -async def test_Graph(event_loop, server, client, form_type): +@pytest.mark.asyncio(loop_scope="session") +async def test_Graph(server, client, form_type): if form_type != "multipart/form-data": pytest.skip() diff --git a/tests/stream_test.py b/tests/stream_test.py index 42e080f9..37aa3ca9 100644 --- a/tests/stream_test.py +++ b/tests/stream_test.py @@ -1,6 +1,5 @@ import asyncio import random -import sys import string from typing import Annotated @@ -31,10 +30,9 @@ def config(unused_tcp_port_factory): return c -@pytest_asyncio.fixture(scope="session") -async def server(event_loop, config): - policy = asyncio.get_event_loop_policy() - asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) +@pytest_asyncio.fixture(loop_scope="session") +async def server(config): + event_loop = asyncio.get_event_loop() try: sd = asyncio.Event() task = event_loop.create_task(serve(app, config, shutdown_trigger=sd.wait)) @@ -42,10 +40,14 @@ async def server(event_loop, config): finally: sd.set() await task - asyncio.set_event_loop_policy(policy) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") +def event_loop_policy(): + return uvloop.EventLoopPolicy() + + +@pytest_asyncio.fixture(loop_scope="session") async def client(event_loop, server): api = await aiopenapi3.OpenAPI.load_async(f"http://{server.bind[0]}/openapi.json") return api @@ -83,8 +85,8 @@ def request_streaming( return r + len(path) -@pytest.mark.asyncio -async def test_stream_data(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_stream_data(server, client): cl = client._max_response_content_length req = client.createRequest("file") headers, schema_, session, result = await req.stream(parameters=dict(content_length=cl)) @@ -98,8 +100,8 @@ async def test_stream_data(event_loop, server, client): assert l == cl -@pytest.mark.asyncio -async def test_request(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_request(server, client): import io data = [ @@ -111,8 +113,8 @@ async def test_request(event_loop, server, client): assert size == 24 -@pytest.mark.asyncio -async def test_stream_array(event_loop, server, client): +@pytest.mark.asyncio(loop_scope="session") +async def test_stream_array(server, client): import ijson req = client.createRequest("files") @@ -147,9 +149,8 @@ def cb(): coro.close() -@pytest.mark.asyncio -@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires asyncio.to_thread") -async def test_sync_stream(event_loop, server): +@pytest.mark.asyncio(loop_scope="session") +async def test_sync_stream(server): client = await asyncio.to_thread( aiopenapi3.OpenAPI.load_sync, f"http://{server.bind[0]}/openapi.json", diff --git a/tests/tls_test.py b/tests/tls_test.py index b3745128..11dc9f7a 100644 --- a/tests/tls_test.py +++ b/tests/tls_test.py @@ -2,7 +2,6 @@ import io import ssl from pathlib import Path -import sys import httpx import pytest @@ -77,10 +76,9 @@ def create_ssl_context(self): return c -@pytest_asyncio.fixture(scope="session") -async def server(event_loop, config): - policy = asyncio.get_event_loop_policy() - asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) +@pytest_asyncio.fixture(loop_scope="session") +async def server(config): + event_loop = asyncio.get_event_loop() try: sd = asyncio.Event() task = event_loop.create_task(serve(app, config, shutdown_trigger=sd.wait)) @@ -88,7 +86,11 @@ async def server(event_loop, config): finally: sd.set() await task - asyncio.set_event_loop_policy(policy) + + +@pytest.fixture(scope="session") +def event_loop_policy(): + return uvloop.EventLoopPolicy() class MutualTLSSecurity(Document): @@ -117,8 +119,8 @@ def parsed(self, ctx: "Document.Context") -> "Document.Context": return ctx -@pytest_asyncio.fixture(scope="session") -async def client(event_loop, server, certs): +@pytest_asyncio.fixture(loop_scope="session") +async def client(server, certs): def self_signed(*args, **kwargs) -> httpx.AsyncClient: ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=certs["org"]["issuer"]) if (cert := kwargs.get("cert", None)) is not None: @@ -163,8 +165,8 @@ def optional_tls(request: Request, response: Response) -> str: return x509.subject.rfc4514_string() -@pytest.mark.asyncio -async def test_tls_required(event_loop, server, client, certs): +@pytest.mark.asyncio(loop_scope="session") +async def test_tls_required(server, client, certs): with pytest.raises(ValueError, match=r"No security requirement satisfied \(accepts {tls}\)"): client.authenticate(None) await client._.required_tls_authentication() @@ -178,8 +180,8 @@ async def test_tls_required(event_loop, server, client, certs): assert l is not None -@pytest.mark.asyncio -async def test_tls_optional(event_loop, server, client, certs): +@pytest.mark.asyncio(loop_scope="session") +async def test_tls_optional(server, client, certs): client.authenticate(None) l = await client._.optional_tls_authentication() assert l is None @@ -193,9 +195,8 @@ async def test_tls_optional(event_loop, server, client, certs): await client._.required_tls_authentication() -@pytest.mark.asyncio -@pytest.mark.skipif(sys.version_info < (3, 9), reason="requires asyncio.to_thread") -async def test_sync(event_loop, server, certs): +@pytest.mark.asyncio(loop_scope="session") +async def test_sync(server, certs): def self_signed_(*args, **kwargs) -> httpx.Client: ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=certs["org"]["issuer"]) if (cert := kwargs.get("cert", None)) is not None: