From 2c89efa95aab0c5357a107b082c1b06350767f25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francis=20Clairicia-Rose-Claire-Jos=C3=A9phine?= Date: Thu, 25 Apr 2024 12:20:54 +0200 Subject: [PATCH] Optional async-backend interface (#279) --- README.md | 7 +- .../basics/api_async/connection_example1.py | 4 +- .../basics/api_async/connection_example2.py | 4 +- .../basics/api_async/socket_example1.py | 4 +- .../concurrency/ssl_shared_lock.py | 3 - .../howto/tcp_clients/usage/api_async.py | 7 +- .../howto/tcp_servers/async_server.py | 4 +- .../howto/tcp_servers/background_server.py | 12 +-- .../tcp_servers/background_server_ssl.py | 12 +-- .../basics/api_async/connection_example1.py | 4 +- .../basics/api_async/socket_example1.py | 4 +- .../howto/udp_servers/async_server.py | 4 +- .../howto/udp_servers/background_server.py | 13 ++- .../echo_client_server_tcp/async_client.py | 4 +- .../echo_client_server_tcp/async_server.py | 4 +- .../echo_client_server_udp/async_client.py | 4 +- .../echo_client_server_udp/async_server.py | 4 +- .../tutorials/ftp_server/async_server.py | 2 - docs/source/howto/tcp_clients.rst | 2 +- docs/source/howto/tcp_servers.rst | 2 +- docs/source/quickstart/install.rst | 1 - src/easynetwork/clients/async_tcp.py | 11 +-- src/easynetwork/clients/async_udp.py | 11 +-- .../api_async/backend/_sniffio_helpers.py | 19 ++++- .../lowlevel/api_async/backend/utils.py | 53 ++++++++++++ src/easynetwork/lowlevel/futures.py | 7 +- src/easynetwork/servers/_base.py | 14 +--- src/easynetwork/servers/async_tcp.py | 7 +- src/easynetwork/servers/async_udp.py | 7 +- src/easynetwork/servers/standalone_tcp.py | 3 +- src/easynetwork/servers/standalone_udp.py | 3 +- .../test_async/test_futures.py | 2 +- .../test_api/test_client/test_tcp.py | 2 +- .../test_api/test_client/test_udp.py | 2 +- .../test_api/test_server/test_tcp.py | 2 +- .../test_api/test_server/test_udp.py | 2 +- .../test_backend/test_sniffio_helpers.py | 46 +++++++++++ .../test_backend/test_utils.py | 82 +++++++++++++++++++ .../test_lowlevel_api/test_futures.py | 2 +- .../test_sync/test_server/test_standalone.py | 4 +- 40 files changed, 268 insertions(+), 116 deletions(-) create mode 100644 src/easynetwork/lowlevel/api_async/backend/utils.py create mode 100644 tests/unit_test/test_async/test_lowlevel_api/test_backend/test_sniffio_helpers.py create mode 100644 tests/unit_test/test_async/test_lowlevel_api/test_backend/test_utils.py diff --git a/README.md b/README.md index 0f3ad5ec..d4d60e41 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,6 @@ import logging from collections.abc import AsyncGenerator from typing import Any, TypeAlias -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import StreamProtocol from easynetwork.serializers import JSONSerializer from easynetwork.servers import AsyncTCPNetworkServer @@ -101,14 +100,13 @@ async def main() -> None: port = 9000 protocol = JSONProtocol() handler = EchoRequestHandler() - backend = AsyncIOBackend() logging.basicConfig( level=logging.INFO, format="[ %(levelname)s ] [ %(name)s ] %(message)s", ) - async with AsyncTCPNetworkServer(host, port, protocol, handler, backend) as server: + async with AsyncTCPNetworkServer(host, port, protocol, handler) as server: try: await server.serve_forever() except asyncio.CancelledError: @@ -154,12 +152,11 @@ if __name__ == "__main__": import asyncio from easynetwork.clients import AsyncTCPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend ... async def main() -> None: - async with AsyncTCPNetworkClient(("localhost", 9000), JSONProtocol(), AsyncIOBackend()) as client: + async with AsyncTCPNetworkClient(("localhost", 9000), JSONProtocol()) as client: await client.send_packet({"data": {"my_body": ["as json"]}}) response = await client.recv_packet() # response should be the sent dictionary print(response) # prints {'data': {'my_body': ['as json']}} diff --git a/docs/source/_include/examples/howto/tcp_clients/basics/api_async/connection_example1.py b/docs/source/_include/examples/howto/tcp_clients/basics/api_async/connection_example1.py index 1e08fdb6..4adfc65c 100644 --- a/docs/source/_include/examples/howto/tcp_clients/basics/api_async/connection_example1.py +++ b/docs/source/_include/examples/howto/tcp_clients/basics/api_async/connection_example1.py @@ -3,7 +3,6 @@ import asyncio from easynetwork.clients import AsyncTCPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import StreamProtocol from easynetwork.serializers import JSONSerializer @@ -11,9 +10,8 @@ async def main() -> None: protocol = StreamProtocol(JSONSerializer()) address = ("localhost", 9000) - backend = AsyncIOBackend() - async with AsyncTCPNetworkClient(address, protocol, backend) as client: + async with AsyncTCPNetworkClient(address, protocol) as client: print(f"Remote address: {client.get_remote_address()}") ... diff --git a/docs/source/_include/examples/howto/tcp_clients/basics/api_async/connection_example2.py b/docs/source/_include/examples/howto/tcp_clients/basics/api_async/connection_example2.py index 235e0d4e..2f358591 100644 --- a/docs/source/_include/examples/howto/tcp_clients/basics/api_async/connection_example2.py +++ b/docs/source/_include/examples/howto/tcp_clients/basics/api_async/connection_example2.py @@ -3,7 +3,6 @@ import asyncio from easynetwork.clients import AsyncTCPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import StreamProtocol from easynetwork.serializers import JSONSerializer @@ -11,11 +10,10 @@ async def main() -> None: protocol = StreamProtocol(JSONSerializer()) address = ("localhost", 9000) - backend = AsyncIOBackend() try: async with asyncio.timeout(30): - client = AsyncTCPNetworkClient(address, protocol, backend) + client = AsyncTCPNetworkClient(address, protocol) await client.wait_connected() except TimeoutError: print(f"Could not connect to {address} after 30 seconds") diff --git a/docs/source/_include/examples/howto/tcp_clients/basics/api_async/socket_example1.py b/docs/source/_include/examples/howto/tcp_clients/basics/api_async/socket_example1.py index fff0d37c..c10a843a 100644 --- a/docs/source/_include/examples/howto/tcp_clients/basics/api_async/socket_example1.py +++ b/docs/source/_include/examples/howto/tcp_clients/basics/api_async/socket_example1.py @@ -4,7 +4,6 @@ import socket from easynetwork.clients import AsyncTCPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import StreamProtocol from easynetwork.serializers import JSONSerializer @@ -20,9 +19,8 @@ async def obtain_a_connected_socket() -> socket.socket: async def main() -> None: protocol = StreamProtocol(JSONSerializer()) sock = await obtain_a_connected_socket() - backend = AsyncIOBackend() - async with AsyncTCPNetworkClient(sock, protocol, backend) as client: + async with AsyncTCPNetworkClient(sock, protocol) as client: print(f"Remote address: {client.get_remote_address()}") ... diff --git a/docs/source/_include/examples/howto/tcp_clients/concurrency/ssl_shared_lock.py b/docs/source/_include/examples/howto/tcp_clients/concurrency/ssl_shared_lock.py index e4f9df14..a063b079 100644 --- a/docs/source/_include/examples/howto/tcp_clients/concurrency/ssl_shared_lock.py +++ b/docs/source/_include/examples/howto/tcp_clients/concurrency/ssl_shared_lock.py @@ -1,7 +1,6 @@ from __future__ import annotations from easynetwork.clients import AsyncTCPNetworkClient, TCPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import StreamProtocol from easynetwork.serializers import JSONSerializer @@ -22,13 +21,11 @@ def ssl_shared_lock_for_sync_client() -> None: async def ssl_shared_lock_for_async_client() -> None: remote_address = ("remote_address", 12345) protocol = StreamProtocol(JSONSerializer()) - backend = AsyncIOBackend() # [start] client = AsyncTCPNetworkClient( remote_address, protocol, - backend, ssl=True, ssl_shared_lock=False, ) diff --git a/docs/source/_include/examples/howto/tcp_clients/usage/api_async.py b/docs/source/_include/examples/howto/tcp_clients/usage/api_async.py index 62dc5162..168fc34f 100644 --- a/docs/source/_include/examples/howto/tcp_clients/usage/api_async.py +++ b/docs/source/_include/examples/howto/tcp_clients/usage/api_async.py @@ -6,7 +6,6 @@ from easynetwork.clients import AsyncTCPNetworkClient from easynetwork.exceptions import StreamProtocolParseError -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import StreamProtocol from easynetwork.serializers import JSONSerializer @@ -78,10 +77,9 @@ async def socket_proxy_example(client: AsyncTCPNetworkClient[Any, Any]) -> None: async def max_recv_size_example() -> None: address = ("remote_address", 12345) protocol = StreamProtocol(JSONSerializer()) - backend = AsyncIOBackend() # [start] - async with AsyncTCPNetworkClient(address, protocol, backend, max_recv_size=1024) as client: + async with AsyncTCPNetworkClient(address, protocol, max_recv_size=1024) as client: # Only do socket.recv(1024) calls packet = await client.recv_packet() @@ -89,10 +87,9 @@ async def max_recv_size_example() -> None: async def ssl_default_context_example() -> None: address = ("remote_address", 12345) protocol = StreamProtocol(JSONSerializer()) - backend = AsyncIOBackend() # [start] - async with AsyncTCPNetworkClient(address, protocol, backend, ssl=True) as client: + async with AsyncTCPNetworkClient(address, protocol, ssl=True) as client: await client.send_packet({"data": 42}) packet = await client.recv_packet() diff --git a/docs/source/_include/examples/howto/tcp_servers/async_server.py b/docs/source/_include/examples/howto/tcp_servers/async_server.py index 373d469e..d9a80c62 100644 --- a/docs/source/_include/examples/howto/tcp_servers/async_server.py +++ b/docs/source/_include/examples/howto/tcp_servers/async_server.py @@ -3,7 +3,6 @@ import asyncio from collections.abc import AsyncGenerator -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import StreamProtocol from easynetwork.servers import AsyncTCPNetworkServer from easynetwork.servers.handlers import AsyncStreamClient, AsyncStreamRequestHandler @@ -36,10 +35,9 @@ async def main() -> None: host, port = "localhost", 9000 protocol = ServerProtocol() handler = MyRequestHandler() - backend = AsyncIOBackend() # Create the server, binding to localhost on port 9000 - async with AsyncTCPNetworkServer(host, port, protocol, handler, backend) as server: + async with AsyncTCPNetworkServer(host, port, protocol, handler) as server: # Activate the server; this will keep running until you # interrupt the program with Ctrl-C await server.serve_forever() diff --git a/docs/source/_include/examples/howto/tcp_servers/background_server.py b/docs/source/_include/examples/howto/tcp_servers/background_server.py index fa24d1d4..8c446874 100644 --- a/docs/source/_include/examples/howto/tcp_servers/background_server.py +++ b/docs/source/_include/examples/howto/tcp_servers/background_server.py @@ -5,7 +5,6 @@ from typing import Any from easynetwork.clients import AsyncTCPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import StreamProtocol from easynetwork.serializers import JSONSerializer from easynetwork.servers import AsyncTCPNetworkServer @@ -30,11 +29,10 @@ async def handle( await client.send_packet({"task": current_task.get_name(), "request": request}) -async def client(host: str, port: int, message: str, backend: AsyncIOBackend) -> None: +async def client(host: str, port: int, message: str) -> None: async with AsyncTCPNetworkClient( (host, port), JSONProtocol(), - backend, ) as client: await client.send_packet({"message": message}) response = await client.recv_packet() @@ -45,14 +43,12 @@ async def main() -> None: host, port = "localhost", 9000 protocol = JSONProtocol() handler = MyRequestHandler() - backend = AsyncIOBackend() server = AsyncTCPNetworkServer( host, port, protocol, handler, - backend, ) async with server: @@ -62,9 +58,9 @@ async def main() -> None: print(f"Server loop running in task: {server_task.get_name()}") - await client(host, port, "Hello world 1", backend) - await client(host, port, "Hello world 2", backend) - await client(host, port, "Hello world 3", backend) + await client(host, port, "Hello world 1") + await client(host, port, "Hello world 2") + await client(host, port, "Hello world 3") await server.shutdown() diff --git a/docs/source/_include/examples/howto/tcp_servers/background_server_ssl.py b/docs/source/_include/examples/howto/tcp_servers/background_server_ssl.py index 74096c65..51f68306 100644 --- a/docs/source/_include/examples/howto/tcp_servers/background_server_ssl.py +++ b/docs/source/_include/examples/howto/tcp_servers/background_server_ssl.py @@ -6,7 +6,6 @@ from typing import Any from easynetwork.clients import AsyncTCPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import StreamProtocol from easynetwork.serializers import JSONSerializer from easynetwork.servers import AsyncTCPNetworkServer @@ -31,11 +30,10 @@ async def handle( await client.send_packet({"task": current_task.get_name(), "request": request}) -async def client(host: str, port: int, message: str, backend: AsyncIOBackend) -> None: +async def client(host: str, port: int, message: str) -> None: async with AsyncTCPNetworkClient( (host, port), JSONProtocol(), - backend, ssl=True, ) as client: await client.send_packet({"message": message}) @@ -47,7 +45,6 @@ async def main() -> None: host, port = "localhost", 9000 protocol = JSONProtocol() handler = MyRequestHandler() - backend = AsyncIOBackend() ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_context.load_cert_chain( @@ -59,7 +56,6 @@ async def main() -> None: port, protocol, handler, - backend, ssl=ssl_context, ) @@ -70,9 +66,9 @@ async def main() -> None: print(f"Server loop running in task: {server_task.get_name()}") - await client(host, port, "Hello world 1", backend) - await client(host, port, "Hello world 2", backend) - await client(host, port, "Hello world 3", backend) + await client(host, port, "Hello world 1") + await client(host, port, "Hello world 2") + await client(host, port, "Hello world 3") await server.shutdown() diff --git a/docs/source/_include/examples/howto/udp_clients/basics/api_async/connection_example1.py b/docs/source/_include/examples/howto/udp_clients/basics/api_async/connection_example1.py index 31b5ea74..976dd958 100644 --- a/docs/source/_include/examples/howto/udp_clients/basics/api_async/connection_example1.py +++ b/docs/source/_include/examples/howto/udp_clients/basics/api_async/connection_example1.py @@ -3,7 +3,6 @@ import asyncio from easynetwork.clients import AsyncUDPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import DatagramProtocol from easynetwork.serializers import JSONSerializer @@ -11,9 +10,8 @@ async def main() -> None: protocol = DatagramProtocol(JSONSerializer()) address = ("localhost", 9000) - backend = AsyncIOBackend() - async with AsyncUDPNetworkClient(address, protocol, backend) as client: + async with AsyncUDPNetworkClient(address, protocol) as client: print(f"Remote address: {client.get_remote_address()}") ... diff --git a/docs/source/_include/examples/howto/udp_clients/basics/api_async/socket_example1.py b/docs/source/_include/examples/howto/udp_clients/basics/api_async/socket_example1.py index 0f5a9a6b..fae1d719 100644 --- a/docs/source/_include/examples/howto/udp_clients/basics/api_async/socket_example1.py +++ b/docs/source/_include/examples/howto/udp_clients/basics/api_async/socket_example1.py @@ -4,7 +4,6 @@ import socket from easynetwork.clients import AsyncUDPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import DatagramProtocol from easynetwork.serializers import JSONSerializer @@ -20,9 +19,8 @@ async def obtain_a_connected_socket() -> socket.socket: async def main() -> None: protocol = DatagramProtocol(JSONSerializer()) sock = await obtain_a_connected_socket() - backend = AsyncIOBackend() - async with AsyncUDPNetworkClient(sock, protocol, backend) as client: + async with AsyncUDPNetworkClient(sock, protocol) as client: print(f"Remote address: {client.get_remote_address()}") ... diff --git a/docs/source/_include/examples/howto/udp_servers/async_server.py b/docs/source/_include/examples/howto/udp_servers/async_server.py index 564a0843..1e24dc2a 100644 --- a/docs/source/_include/examples/howto/udp_servers/async_server.py +++ b/docs/source/_include/examples/howto/udp_servers/async_server.py @@ -3,7 +3,6 @@ import asyncio from collections.abc import AsyncGenerator -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import DatagramProtocol from easynetwork.servers import AsyncUDPNetworkServer from easynetwork.servers.handlers import AsyncDatagramClient, AsyncDatagramRequestHandler @@ -36,10 +35,9 @@ async def main() -> None: host, port = "localhost", 9000 protocol = ServerProtocol() handler = MyRequestHandler() - backend = AsyncIOBackend() # Create the server, binding to localhost on port 9000 - async with AsyncUDPNetworkServer(host, port, protocol, handler, backend) as server: + async with AsyncUDPNetworkServer(host, port, protocol, handler) as server: # Activate the server; this will keep running until you # interrupt the program with Ctrl-C await server.serve_forever() diff --git a/docs/source/_include/examples/howto/udp_servers/background_server.py b/docs/source/_include/examples/howto/udp_servers/background_server.py index 889c2ddc..a7d2df80 100644 --- a/docs/source/_include/examples/howto/udp_servers/background_server.py +++ b/docs/source/_include/examples/howto/udp_servers/background_server.py @@ -5,7 +5,6 @@ from typing import Any from easynetwork.clients import AsyncUDPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.protocol import DatagramProtocol from easynetwork.serializers import JSONSerializer from easynetwork.servers import AsyncUDPNetworkServer @@ -30,8 +29,8 @@ async def handle( await client.send_packet({"task": current_task.get_name(), "request": request}) -async def client(host: str, port: int, message: str, backend: AsyncIOBackend) -> None: - async with AsyncUDPNetworkClient((host, port), JSONProtocol(), backend) as client: +async def client(host: str, port: int, message: str) -> None: + async with AsyncUDPNetworkClient((host, port), JSONProtocol()) as client: await client.send_packet({"message": message}) response = await client.recv_packet() print(f"From server: {response}") @@ -41,14 +40,12 @@ async def main() -> None: host, port = "localhost", 9000 protocol = JSONProtocol() handler = MyRequestHandler() - backend = AsyncIOBackend() server = AsyncUDPNetworkServer( host, port, protocol, handler, - backend, ) async with server: @@ -58,9 +55,9 @@ async def main() -> None: print(f"Server loop running in task: {server_task.get_name()}") - await client(host, port, "Hello world 1", backend) - await client(host, port, "Hello world 2", backend) - await client(host, port, "Hello world 3", backend) + await client(host, port, "Hello world 1") + await client(host, port, "Hello world 2") + await client(host, port, "Hello world 3") await server.shutdown() diff --git a/docs/source/_include/examples/tutorials/echo_client_server_tcp/async_client.py b/docs/source/_include/examples/tutorials/echo_client_server_tcp/async_client.py index 325a69ec..8c4d593c 100644 --- a/docs/source/_include/examples/tutorials/echo_client_server_tcp/async_client.py +++ b/docs/source/_include/examples/tutorials/echo_client_server_tcp/async_client.py @@ -4,7 +4,6 @@ import sys from easynetwork.clients import AsyncTCPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from json_protocol import JSONProtocol @@ -13,10 +12,9 @@ async def main() -> None: host = "localhost" port = 9000 protocol = JSONProtocol() - backend = AsyncIOBackend() # Connect to server - async with AsyncTCPNetworkClient((host, port), protocol, backend) as client: + async with AsyncTCPNetworkClient((host, port), protocol) as client: # Send data request = {"command-line arguments": sys.argv[1:]} await client.send_packet(request) diff --git a/docs/source/_include/examples/tutorials/echo_client_server_tcp/async_server.py b/docs/source/_include/examples/tutorials/echo_client_server_tcp/async_server.py index 73403873..398d1b28 100644 --- a/docs/source/_include/examples/tutorials/echo_client_server_tcp/async_server.py +++ b/docs/source/_include/examples/tutorials/echo_client_server_tcp/async_server.py @@ -2,7 +2,6 @@ import asyncio -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.servers import AsyncTCPNetworkServer from echo_request_handler import EchoRequestHandler @@ -14,9 +13,8 @@ async def main() -> None: port = 9000 protocol = JSONProtocol() handler = EchoRequestHandler() - backend = AsyncIOBackend() - async with AsyncTCPNetworkServer(host, port, protocol, handler, backend) as server: + async with AsyncTCPNetworkServer(host, port, protocol, handler) as server: try: await server.serve_forever() except asyncio.CancelledError: diff --git a/docs/source/_include/examples/tutorials/echo_client_server_udp/async_client.py b/docs/source/_include/examples/tutorials/echo_client_server_udp/async_client.py index f428413e..a102e5c3 100644 --- a/docs/source/_include/examples/tutorials/echo_client_server_udp/async_client.py +++ b/docs/source/_include/examples/tutorials/echo_client_server_udp/async_client.py @@ -4,7 +4,6 @@ import sys from easynetwork.clients import AsyncUDPNetworkClient -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from json_protocol import JSONDatagramProtocol @@ -13,10 +12,9 @@ async def main() -> None: host = "localhost" port = 9000 protocol = JSONDatagramProtocol() - backend = AsyncIOBackend() # Connect to server - async with AsyncUDPNetworkClient((host, port), protocol, backend) as client: + async with AsyncUDPNetworkClient((host, port), protocol) as client: # Send data request = {"command-line arguments": sys.argv[1:]} await client.send_packet(request) diff --git a/docs/source/_include/examples/tutorials/echo_client_server_udp/async_server.py b/docs/source/_include/examples/tutorials/echo_client_server_udp/async_server.py index 58bf42ce..44c6daca 100644 --- a/docs/source/_include/examples/tutorials/echo_client_server_udp/async_server.py +++ b/docs/source/_include/examples/tutorials/echo_client_server_udp/async_server.py @@ -2,7 +2,6 @@ import asyncio -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.servers import AsyncUDPNetworkServer from echo_request_handler import EchoRequestHandler @@ -14,9 +13,8 @@ async def main() -> None: port = 9000 protocol = JSONDatagramProtocol() handler = EchoRequestHandler() - backend = AsyncIOBackend() - async with AsyncUDPNetworkServer(host, port, protocol, handler, backend) as server: + async with AsyncUDPNetworkServer(host, port, protocol, handler) as server: try: await server.serve_forever() except asyncio.CancelledError: diff --git a/docs/source/_include/examples/tutorials/ftp_server/async_server.py b/docs/source/_include/examples/tutorials/ftp_server/async_server.py index 898bfbef..78047b21 100644 --- a/docs/source/_include/examples/tutorials/ftp_server/async_server.py +++ b/docs/source/_include/examples/tutorials/ftp_server/async_server.py @@ -2,7 +2,6 @@ from collections.abc import Sequence -from easynetwork.lowlevel.std_asyncio import AsyncIOBackend from easynetwork.servers import AsyncTCPNetworkServer from ftp_reply import FTPReply @@ -22,7 +21,6 @@ def __init__( port, FTPServerProtocol(), FTPRequestHandler(), - AsyncIOBackend(), ) diff --git a/docs/source/howto/tcp_clients.rst b/docs/source/howto/tcp_clients.rst index c0ea3ac3..206af826 100644 --- a/docs/source/howto/tcp_clients.rst +++ b/docs/source/howto/tcp_clients.rst @@ -53,7 +53,7 @@ You need the host address (domain name or IP) and the port of connection in orde .. literalinclude:: ../_include/examples/howto/tcp_clients/basics/api_async/connection_example2.py :pyobject: main :lineno-match: - :emphasize-lines: 6-14 + :emphasize-lines: 5-13 .. note:: diff --git a/docs/source/howto/tcp_servers.rst b/docs/source/howto/tcp_servers.rst index 35e69541..54d6385b 100644 --- a/docs/source/howto/tcp_servers.rst +++ b/docs/source/howto/tcp_servers.rst @@ -295,4 +295,4 @@ If you want your client to make an SSL connection, you need to pass an :class:`~ .. literalinclude:: ../_include/examples/howto/tcp_servers/background_server_ssl.py :linenos: - :emphasize-lines: 39,52-56,63 + :emphasize-lines: 37,49-53,59 diff --git a/docs/source/quickstart/install.rst b/docs/source/quickstart/install.rst index 93532990..e2968d13 100644 --- a/docs/source/quickstart/install.rst +++ b/docs/source/quickstart/install.rst @@ -39,4 +39,3 @@ Example where the ``cbor`` and ``msgpack`` extensions are installed: .. todo:: Explain what we do with ``sniffio`` when adding documentation of ``AsyncIOBackend`` - (because it is *NOT* for ``sniffio.current_async_library()``). diff --git a/src/easynetwork/clients/async_tcp.py b/src/easynetwork/clients/async_tcp.py index dceb4a6d..3632ffaa 100644 --- a/src/easynetwork/clients/async_tcp.py +++ b/src/easynetwork/clients/async_tcp.py @@ -37,6 +37,7 @@ from ..exceptions import ClientClosedError from ..lowlevel import _utils, constants from ..lowlevel.api_async.backend.abc import AsyncBackend, CancelScope, ILock +from ..lowlevel.api_async.backend.utils import BuiltinAsyncBackendToken, ensure_backend from ..lowlevel.api_async.endpoints.stream import AsyncStreamEndpoint from ..lowlevel.api_async.transports.abc import AsyncStreamTransport from ..lowlevel.socket import ( @@ -90,7 +91,7 @@ def __init__( address: tuple[str, int], /, protocol: StreamProtocol[_T_SentPacket, _T_ReceivedPacket], - backend: AsyncBackend, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = ..., *, local_address: tuple[str, int] | None = ..., happy_eyeballs_delay: float | None = ..., @@ -110,7 +111,7 @@ def __init__( socket: _socket.socket, /, protocol: StreamProtocol[_T_SentPacket, _T_ReceivedPacket], - backend: AsyncBackend, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = ..., *, ssl: _typing_ssl.SSLContext | bool | None = ..., server_hostname: str | None = ..., @@ -127,7 +128,7 @@ def __init__( __arg: tuple[str, int] | _socket.socket, /, protocol: StreamProtocol[_T_SentPacket, _T_ReceivedPacket], - backend: AsyncBackend, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = None, *, ssl: _typing_ssl.SSLContext | bool | None = None, server_hostname: str | None = None, @@ -190,8 +191,8 @@ def __init__( if not isinstance(protocol, StreamProtocol): raise TypeError(f"Expected a StreamProtocol object, got {protocol!r}") - if not isinstance(backend, AsyncBackend): - raise TypeError(f"Expected an AsyncBackend instance, got {backend!r}") + + backend = ensure_backend(backend) if max_recv_size is None: max_recv_size = constants.DEFAULT_STREAM_BUFSIZE diff --git a/src/easynetwork/clients/async_udp.py b/src/easynetwork/clients/async_udp.py index f6a518c6..d7b7be46 100644 --- a/src/easynetwork/clients/async_udp.py +++ b/src/easynetwork/clients/async_udp.py @@ -29,6 +29,7 @@ from ..exceptions import ClientClosedError from ..lowlevel import _utils, constants from ..lowlevel.api_async.backend.abc import AsyncBackend, CancelScope, ILock +from ..lowlevel.api_async.backend.utils import BuiltinAsyncBackendToken, ensure_backend from ..lowlevel.api_async.endpoints.datagram import AsyncDatagramEndpoint from ..lowlevel.api_async.transports.abc import AsyncDatagramTransport from ..lowlevel.socket import INETSocketAttribute, SocketAddress, SocketProxy, new_socket_address @@ -70,7 +71,7 @@ def __init__( address: tuple[str, int], /, protocol: DatagramProtocol[_T_SentPacket, _T_ReceivedPacket], - backend: AsyncBackend, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = ..., *, local_address: tuple[str, int] | None = ..., family: int = ..., @@ -82,7 +83,7 @@ def __init__( socket: _socket.socket, /, protocol: DatagramProtocol[_T_SentPacket, _T_ReceivedPacket], - backend: AsyncBackend, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = ..., ) -> None: ... def __init__( @@ -90,7 +91,7 @@ def __init__( __arg: tuple[str, int] | _socket.socket, /, protocol: DatagramProtocol[_T_SentPacket, _T_ReceivedPacket], - backend: AsyncBackend, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = None, **kwargs: Any, ) -> None: """ @@ -114,8 +115,8 @@ def __init__( if not isinstance(protocol, DatagramProtocol): raise TypeError(f"Expected a DatagramProtocol object, got {protocol!r}") - if not isinstance(backend, AsyncBackend): - raise TypeError(f"Expected an AsyncBackend instance, got {backend!r}") + + backend = ensure_backend(backend) self.__backend: AsyncBackend = backend self.__protocol: DatagramProtocol[_T_SentPacket, _T_ReceivedPacket] = protocol diff --git a/src/easynetwork/lowlevel/api_async/backend/_sniffio_helpers.py b/src/easynetwork/lowlevel/api_async/backend/_sniffio_helpers.py index 22a8a9dc..b9a1f8d5 100644 --- a/src/easynetwork/lowlevel/api_async/backend/_sniffio_helpers.py +++ b/src/easynetwork/lowlevel/api_async/backend/_sniffio_helpers.py @@ -16,18 +16,35 @@ from __future__ import annotations -__all__ = ["setup_sniffio_contextvar"] +__all__ = ["current_async_library", "setup_sniffio_contextvar"] import contextvars +import sys + + +def _current_async_library_fallback() -> str: + if "asyncio" in sys.modules: + import asyncio + + try: + asyncio.get_running_loop() + except RuntimeError: + pass + else: + return "asyncio" + raise RuntimeError("unknown async library, or not in async context") + try: import sniffio except ModuleNotFoundError: + current_async_library = _current_async_library_fallback def setup_sniffio_contextvar(context: contextvars.Context, library_name: str | None, /) -> None: pass else: + current_async_library = sniffio.current_async_library def setup_sniffio_contextvar(context: contextvars.Context, library_name: str | None, /) -> None: context.run(sniffio.current_async_library_cvar.set, library_name) diff --git a/src/easynetwork/lowlevel/api_async/backend/utils.py b/src/easynetwork/lowlevel/api_async/backend/utils.py new file mode 100644 index 00000000..d2455baf --- /dev/null +++ b/src/easynetwork/lowlevel/api_async/backend/utils.py @@ -0,0 +1,53 @@ +# Copyright 2021-2024, Francis Clairicia-Rose-Claire-Josephine +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +"""Helper module for async backend""" + +from __future__ import annotations + +__all__ = [ + "BuiltinAsyncBackendToken", + "ensure_backend", +] + +from typing import Literal, TypeAlias + +from . import _sniffio_helpers +from .abc import AsyncBackend + +BuiltinAsyncBackendToken: TypeAlias = Literal["asyncio"] + + +def ensure_backend(backend: AsyncBackend | BuiltinAsyncBackendToken | None) -> AsyncBackend: + """ + TODO: Add docstring + """ + if backend is None: + match _sniffio_helpers.current_async_library(): + case "asyncio" as backend: + pass + case unsupported_backend: + raise NotImplementedError(unsupported_backend) + + match backend: + case "asyncio": + from ...std_asyncio.backend import AsyncIOBackend + + return AsyncIOBackend() + case AsyncBackend(): + return backend + case str(): + raise NotImplementedError(backend) + case _: + raise TypeError(f"Expected either a string literal or a backend instance, got {backend!r}") diff --git a/src/easynetwork/lowlevel/futures.py b/src/easynetwork/lowlevel/futures.py index 42a86ca7..483fc299 100644 --- a/src/easynetwork/lowlevel/futures.py +++ b/src/easynetwork/lowlevel/futures.py @@ -29,6 +29,7 @@ from .api_async.backend import _sniffio_helpers from .api_async.backend.abc import AsyncBackend +from .api_async.backend.utils import BuiltinAsyncBackendToken, ensure_backend if TYPE_CHECKING: from types import TracebackType @@ -71,7 +72,7 @@ async def main() -> None: def __init__( self, executor: _T_Executor, - backend: AsyncBackend, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = None, *, handle_contexts: bool = True, ) -> None: @@ -85,10 +86,8 @@ def __init__( """ if not isinstance(executor, concurrent.futures.Executor): raise TypeError("Invalid executor type") - if not isinstance(backend, AsyncBackend): - raise TypeError(f"Expected an AsyncBackend instance, got {backend!r}") - self.__backend: AsyncBackend = backend + self.__backend: AsyncBackend = ensure_backend(backend) self.__executor: _T_Executor = executor self.__handle_contexts: bool = bool(handle_contexts) diff --git a/src/easynetwork/servers/_base.py b/src/easynetwork/servers/_base.py index 7ed0a264..f3ead381 100644 --- a/src/easynetwork/servers/_base.py +++ b/src/easynetwork/servers/_base.py @@ -28,6 +28,7 @@ from ..lowlevel import _utils from ..lowlevel._lock import ForkSafeLock from ..lowlevel.api_async.backend.abc import AsyncBackend, ThreadsPortal +from ..lowlevel.api_async.backend.utils import BuiltinAsyncBackendToken, ensure_backend from ..lowlevel.socket import SocketAddress from .abc import AbstractAsyncNetworkServer, AbstractNetworkServer, SupportsEventSet @@ -51,21 +52,14 @@ class BaseStandaloneNetworkServerImpl(AbstractNetworkServer, Generic[_T_AsyncSer def __init__( self, - backend: AsyncBackend | None, + backend: AsyncBackend | BuiltinAsyncBackendToken | None, server_factory: Callable[[AsyncBackend], _T_AsyncServer], *, runner_options: Mapping[str, Any] | None = None, ) -> None: super().__init__() - match backend: - case None: - from ..lowlevel.std_asyncio.backend import AsyncIOBackend - - backend = AsyncIOBackend() - case AsyncBackend(): - pass - case _: - raise TypeError(f"Expected an AsyncBackend instance, got {backend!r}") + + backend = ensure_backend("asyncio" if backend is None else backend) self.__backend: AsyncBackend = backend self.__server_factory: Callable[[AsyncBackend], _T_AsyncServer] = server_factory diff --git a/src/easynetwork/servers/async_tcp.py b/src/easynetwork/servers/async_tcp.py index 3b442295..092bbb4c 100644 --- a/src/easynetwork/servers/async_tcp.py +++ b/src/easynetwork/servers/async_tcp.py @@ -30,6 +30,7 @@ from ..lowlevel import _utils, constants from ..lowlevel._final import runtime_final_class from ..lowlevel.api_async.backend.abc import AsyncBackend, CancelScope, IEvent, Task, TaskGroup +from ..lowlevel.api_async.backend.utils import BuiltinAsyncBackendToken, ensure_backend from ..lowlevel.api_async.servers import stream as _stream_server from ..lowlevel.api_async.transports.abc import AsyncListener, AsyncStreamTransport from ..lowlevel.socket import ( @@ -79,7 +80,7 @@ def __init__( port: int, protocol: StreamProtocol[_T_Response, _T_Request], request_handler: AsyncStreamRequestHandler[_T_Request, _T_Response], - backend: AsyncBackend, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = None, *, ssl: _typing_ssl.SSLContext | None = None, ssl_handshake_timeout: float | None = None, @@ -143,11 +144,11 @@ def __init__( if not isinstance(protocol, StreamProtocol): raise TypeError(f"Expected a StreamProtocol object, got {protocol!r}") - if not isinstance(backend, AsyncBackend): - raise TypeError(f"Expected an AsyncBackend instance, got {backend!r}") if not isinstance(request_handler, AsyncStreamRequestHandler): raise TypeError(f"Expected an AsyncStreamRequestHandler object, got {request_handler!r}") + backend = ensure_backend(backend) + if backlog is None: backlog = 100 diff --git a/src/easynetwork/servers/async_udp.py b/src/easynetwork/servers/async_udp.py index af3fc70a..f63187e9 100644 --- a/src/easynetwork/servers/async_udp.py +++ b/src/easynetwork/servers/async_udp.py @@ -31,6 +31,7 @@ from ..lowlevel import _utils from ..lowlevel._final import runtime_final_class from ..lowlevel.api_async.backend.abc import AsyncBackend, CancelScope, IEvent, Task, TaskGroup +from ..lowlevel.api_async.backend.utils import BuiltinAsyncBackendToken, ensure_backend from ..lowlevel.api_async.servers import datagram as _datagram_server from ..lowlevel.api_async.transports.abc import AsyncDatagramListener from ..lowlevel.socket import INETSocketAttribute, SocketAddress, SocketProxy, new_socket_address @@ -64,7 +65,7 @@ def __init__( port: int, protocol: DatagramProtocol[_T_Response, _T_Request], request_handler: AsyncDatagramRequestHandler[_T_Request, _T_Response], - backend: AsyncBackend, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = None, *, reuse_port: bool = False, logger: logging.Logger | None = None, @@ -89,11 +90,11 @@ def __init__( if not isinstance(protocol, DatagramProtocol): raise TypeError(f"Expected a DatagramProtocol object, got {protocol!r}") - if not isinstance(backend, AsyncBackend): - raise TypeError(f"Expected an AsyncBackend instance, got {backend!r}") if not isinstance(request_handler, AsyncDatagramRequestHandler): raise TypeError(f"Expected an AsyncDatagramRequestHandler object, got {request_handler!r}") + backend = ensure_backend(backend) + self.__backend: AsyncBackend = backend self.__listeners_factory: Callable[[], Coroutine[Any, Any, Sequence[AsyncDatagramListener[tuple[Any, ...]]]]] | None self.__listeners_factory = _utils.make_callback( diff --git a/src/easynetwork/servers/standalone_tcp.py b/src/easynetwork/servers/standalone_tcp.py index 62128fdd..2cfc9b2e 100644 --- a/src/easynetwork/servers/standalone_tcp.py +++ b/src/easynetwork/servers/standalone_tcp.py @@ -25,6 +25,7 @@ from .._typevars import _T_Request, _T_Response from ..lowlevel.api_async.backend.abc import AsyncBackend +from ..lowlevel.api_async.backend.utils import BuiltinAsyncBackendToken from ..lowlevel.socket import SocketProxy from . import _base from .async_tcp import AsyncTCPNetworkServer @@ -55,7 +56,7 @@ def __init__( port: int, protocol: StreamProtocol[_T_Response, _T_Request], request_handler: AsyncStreamRequestHandler[_T_Request, _T_Response], - backend: AsyncBackend | None = None, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = None, *, runner_options: Mapping[str, Any] | None = None, ssl: _SSLContext | None = None, diff --git a/src/easynetwork/servers/standalone_udp.py b/src/easynetwork/servers/standalone_udp.py index 5e76ce3b..250aeb84 100644 --- a/src/easynetwork/servers/standalone_udp.py +++ b/src/easynetwork/servers/standalone_udp.py @@ -25,6 +25,7 @@ from .._typevars import _T_Request, _T_Response from ..lowlevel.api_async.backend.abc import AsyncBackend +from ..lowlevel.api_async.backend.utils import BuiltinAsyncBackendToken from ..lowlevel.socket import SocketProxy from . import _base from .async_udp import AsyncUDPNetworkServer @@ -54,7 +55,7 @@ def __init__( port: int, protocol: DatagramProtocol[_T_Response, _T_Request], request_handler: AsyncDatagramRequestHandler[_T_Request, _T_Response], - backend: AsyncBackend | None = None, + backend: AsyncBackend | BuiltinAsyncBackendToken | None = None, *, runner_options: Mapping[str, Any] | None = None, reuse_port: bool = False, diff --git a/tests/functional_test/test_async/test_futures.py b/tests/functional_test/test_async/test_futures.py index 5e6c56ac..94548e2a 100644 --- a/tests/functional_test/test_async/test_futures.py +++ b/tests/functional_test/test_async/test_futures.py @@ -23,7 +23,7 @@ def max_workers(request: pytest.FixtureRequest) -> int | None: @pytest_asyncio.fixture @staticmethod async def executor(max_workers: int | None) -> AsyncIterator[AsyncExecutor[concurrent.futures.Executor]]: - async with AsyncExecutor(concurrent.futures.ThreadPoolExecutor(max_workers=max_workers), AsyncIOBackend()) as executor: + async with AsyncExecutor(concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)) as executor: yield executor async def test____run____submit_and_wait( diff --git a/tests/unit_test/test_async/test_api/test_client/test_tcp.py b/tests/unit_test/test_async/test_api/test_client/test_tcp.py index b7e8ceb7..c2080025 100644 --- a/tests/unit_test/test_async/test_api/test_client/test_tcp.py +++ b/tests/unit_test/test_async/test_api/test_client/test_tcp.py @@ -355,7 +355,7 @@ async def test____dunder_init____backend____invalid_value( invalid_backend = mocker.NonCallableMagicMock(spec=object) # Act & Assert - with pytest.raises(TypeError, match=r"^Expected an AsyncBackend instance, got .*$"): + with pytest.raises(TypeError, match=r"^Expected either a string literal or a backend instance, got .*$"): if use_socket: _ = AsyncTCPNetworkClient( request.getfixturevalue("mock_tcp_socket"), diff --git a/tests/unit_test/test_async/test_api/test_client/test_udp.py b/tests/unit_test/test_async/test_api/test_client/test_udp.py index 120c8632..d7d4cf03 100644 --- a/tests/unit_test/test_async/test_api/test_client/test_udp.py +++ b/tests/unit_test/test_async/test_api/test_client/test_udp.py @@ -402,7 +402,7 @@ async def test____dunder_init____backend____invalid_value( invalid_backend = mocker.NonCallableMagicMock(spec=object) # Act & Assert - with pytest.raises(TypeError, match=r"^Expected an AsyncBackend instance, got .*$"): + with pytest.raises(TypeError, match=r"^Expected either a string literal or a backend instance, got .*$"): if use_socket: _ = AsyncUDPNetworkClient( request.getfixturevalue("mock_tcp_socket"), diff --git a/tests/unit_test/test_async/test_api/test_server/test_tcp.py b/tests/unit_test/test_async/test_api/test_server/test_tcp.py index dbe18a2a..a168d918 100644 --- a/tests/unit_test/test_async/test_api/test_server/test_tcp.py +++ b/tests/unit_test/test_async/test_api/test_server/test_tcp.py @@ -78,7 +78,7 @@ async def test____dunder_init____backend____invalid_value( invalid_backend = mocker.NonCallableMagicMock(spec=object) # Act & Assert - with pytest.raises(TypeError, match=r"^Expected an AsyncBackend instance, got .*$"): + with pytest.raises(TypeError, match=r"^Expected either a string literal or a backend instance, got .*$"): _ = AsyncTCPNetworkServer(None, 0, mock_stream_protocol, mock_stream_request_handler, invalid_backend) async def test____get_backend____returns_linked_instance( diff --git a/tests/unit_test/test_async/test_api/test_server/test_udp.py b/tests/unit_test/test_async/test_api/test_server/test_udp.py index 9e6ded0d..7f536b89 100644 --- a/tests/unit_test/test_async/test_api/test_server/test_udp.py +++ b/tests/unit_test/test_async/test_api/test_server/test_udp.py @@ -57,7 +57,7 @@ async def test____dunder_init____backend____invalid_value( invalid_backend = mocker.NonCallableMagicMock(spec=object) # Act & Assert - with pytest.raises(TypeError, match=r"^Expected an AsyncBackend instance, got .*$"): + with pytest.raises(TypeError, match=r"^Expected either a string literal or a backend instance, got .*$"): _ = AsyncUDPNetworkServer(None, 0, mock_datagram_protocol, mock_datagram_request_handler, invalid_backend) async def test____get_backend____returns_linked_instance( diff --git a/tests/unit_test/test_async/test_lowlevel_api/test_backend/test_sniffio_helpers.py b/tests/unit_test/test_async/test_lowlevel_api/test_backend/test_sniffio_helpers.py new file mode 100644 index 00000000..53d73eb8 --- /dev/null +++ b/tests/unit_test/test_async/test_lowlevel_api/test_backend/test_sniffio_helpers.py @@ -0,0 +1,46 @@ +from __future__ import annotations + +import sys + +from easynetwork.lowlevel.api_async.backend._sniffio_helpers import _current_async_library_fallback + +import pytest + + +def test____current_async_library_fallback____asyncio_not_imported( + monkeypatch: pytest.MonkeyPatch, +) -> None: + # Arrange + monkeypatch.delitem(sys.modules, "asyncio", raising=False) + + # Act + with pytest.raises(RuntimeError, match=r"^unknown async library, or not in async context$"): + _current_async_library_fallback() + + # Assert + assert "asyncio" not in sys.modules + + +def test____current_async_library_fallback____asyncio_not_running() -> None: + # Arrange + import asyncio + + del asyncio + + # Act & Assert + with pytest.raises(RuntimeError, match=r"^unknown async library, or not in async context$"): + _current_async_library_fallback() + + +def test____current_async_library_fallback____asyncio_is_running() -> None: + # Arrange + import asyncio + + async def main() -> str: + return _current_async_library_fallback() + + # Act + library_name = asyncio.run(main()) + + # Assert + assert library_name == "asyncio" diff --git a/tests/unit_test/test_async/test_lowlevel_api/test_backend/test_utils.py b/tests/unit_test/test_async/test_lowlevel_api/test_backend/test_utils.py new file mode 100644 index 00000000..02436f73 --- /dev/null +++ b/tests/unit_test/test_async/test_lowlevel_api/test_backend/test_utils.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from easynetwork.lowlevel.api_async.backend import _sniffio_helpers +from easynetwork.lowlevel.api_async.backend.utils import ensure_backend +from easynetwork.lowlevel.std_asyncio.backend import AsyncIOBackend + +import pytest + +if TYPE_CHECKING: + from unittest.mock import MagicMock + + from pytest_mock import MockerFixture + + +@pytest.fixture +def mock_current_async_library(mocker: MockerFixture) -> MagicMock: + return mocker.patch(f"{_sniffio_helpers.__name__}.current_async_library", autospec=True) + + +def test____ensure_backend____valid_string_literal(mock_current_async_library: MagicMock) -> None: + # Arrange + + # Act + backend = ensure_backend("asyncio") + + # Assert + assert isinstance(backend, AsyncIOBackend) + mock_current_async_library.assert_not_called() + + +def test____ensure_backend____already_a_backend_instance(mock_backend: MagicMock, mock_current_async_library: MagicMock) -> None: + # Arrange + + # Act + returned_backend = ensure_backend(mock_backend) + + # Assert + assert returned_backend is mock_backend + mock_current_async_library.assert_not_called() + + +def test____ensure_backend____type_error(mocker: MockerFixture, mock_current_async_library: MagicMock) -> None: + # Arrange + invalid_backend = mocker.MagicMock(spec=object) + + # Act & Assert + with pytest.raises(TypeError, match=r"^Expected either a string literal or a backend instance, got .+$"): + _ = ensure_backend(invalid_backend) + mock_current_async_library.assert_not_called() + + +def test____ensure_backend____invalid_string_literal(mock_current_async_library: MagicMock) -> None: + # Arrange + + # Act & Assert + with pytest.raises(NotImplementedError, match=r"^trio$"): + _ = ensure_backend("trio") # type: ignore[arg-type] + mock_current_async_library.assert_not_called() + + +def test____ensure_backend____None____current_async_library_is_supported(mock_current_async_library: MagicMock) -> None: + # Arrange + mock_current_async_library.return_value = "asyncio" + + # Act + backend = ensure_backend(None) + + # Assert + assert isinstance(backend, AsyncIOBackend) + mock_current_async_library.assert_called_once() + + +def test____ensure_backend____None____current_async_library_is_not_supported(mock_current_async_library: MagicMock) -> None: + # Arrange + mock_current_async_library.return_value = "trio" + + # Act & Assert + with pytest.raises(NotImplementedError, match=r"^trio$"): + _ = ensure_backend(None) + mock_current_async_library.assert_called_once() diff --git a/tests/unit_test/test_async/test_lowlevel_api/test_futures.py b/tests/unit_test/test_async/test_lowlevel_api/test_futures.py index 2f5b25cb..19b2c4a4 100644 --- a/tests/unit_test/test_async/test_lowlevel_api/test_futures.py +++ b/tests/unit_test/test_async/test_lowlevel_api/test_futures.py @@ -70,7 +70,7 @@ async def test____dunder_init____invalid_backend( invalid_backend = mocker.NonCallableMagicMock(spec=object) # Act & Assert - with pytest.raises(TypeError, match=r"^Expected an AsyncBackend instance, got .+$"): + with pytest.raises(TypeError, match=r"^Expected either a string literal or a backend instance, got .+$"): _ = AsyncExecutor(mock_stdlib_executor, invalid_backend) async def test____wrapped_property____returned_wrapped_executor_instance( diff --git a/tests/unit_test/test_sync/test_server/test_standalone.py b/tests/unit_test/test_sync/test_server/test_standalone.py index a235215f..80e0e5fc 100644 --- a/tests/unit_test/test_sync/test_server/test_standalone.py +++ b/tests/unit_test/test_sync/test_server/test_standalone.py @@ -32,7 +32,7 @@ def test____dunder_init____backend____invalid_value( invalid_backend = mocker.NonCallableMagicMock(spec=object) # Act & Assert - with pytest.raises(TypeError, match=r"^Expected an AsyncBackend instance, got .*$"): + with pytest.raises(TypeError, match=r"^Expected either a string literal or a backend instance, got .*$"): _ = StandaloneTCPNetworkServer(None, 0, mock_stream_protocol, mock_stream_request_handler, invalid_backend) @pytest.mark.parametrize( @@ -90,7 +90,7 @@ def test____dunder_init____backend____invalid_value( invalid_backend = mocker.NonCallableMagicMock(spec=object) # Act & Assert - with pytest.raises(TypeError, match=r"^Expected an AsyncBackend instance, got .*$"): + with pytest.raises(TypeError, match=r"^Expected either a string literal or a backend instance, got .*$"): _ = StandaloneUDPNetworkServer(None, 0, mock_datagram_protocol, mock_datagram_request_handler, invalid_backend) @pytest.mark.parametrize(