diff --git a/aiosmtpd/__init__.py b/aiosmtpd/__init__.py index 45c43416..7431874e 100644 --- a/aiosmtpd/__init__.py +++ b/aiosmtpd/__init__.py @@ -1,4 +1,21 @@ # Copyright 2014-2021 The aiosmtpd Developers # SPDX-License-Identifier: Apache-2.0 +import asyncio +import warnings -__version__ = "1.4.4a0" + +__version__ = "1.4.4a1" + + +def _get_or_new_eventloop() -> asyncio.AbstractEventLoop: + loop = None + with warnings.catch_warnings(): + warnings.simplefilter("error") + try: + loop = asyncio.get_event_loop() + except (DeprecationWarning, RuntimeError): # pragma: py-lt-310 + if loop is None: # pragma: py-lt-312 + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + assert isinstance(loop, asyncio.AbstractEventLoop) + return loop diff --git a/aiosmtpd/docs/NEWS.rst b/aiosmtpd/docs/NEWS.rst index 31542977..140151f1 100644 --- a/aiosmtpd/docs/NEWS.rst +++ b/aiosmtpd/docs/NEWS.rst @@ -16,10 +16,12 @@ Fixed/Improved * A whole bunch of annotations -1.4.4a0 (ad hoc) +1.4.4a1 (ad hoc) ================ -(Stub ``NEWS.rst`` entry as placeholder for ``qa`` test.) +Fixed/Improved +-------------- +* No longer expect an implicit creation of the event loop through ``get_event_loop()`` (Closes #353) 1.4.3 (2022-12-21) diff --git a/aiosmtpd/docs/controller.rst b/aiosmtpd/docs/controller.rst index 2fb81c02..220d45aa 100644 --- a/aiosmtpd/docs/controller.rst +++ b/aiosmtpd/docs/controller.rst @@ -236,7 +236,8 @@ you'll have to do something similar to this: .. doctest:: unthreaded >>> import asyncio - >>> loop = asyncio.get_event_loop() + >>> loop = asyncio.new_event_loop() + >>> asyncio.set_event_loop(loop) >>> from aiosmtpd.controller import UnthreadedController >>> from aiosmtpd.handlers import Sink >>> controller = UnthreadedController(Sink(), loop=loop) diff --git a/aiosmtpd/handlers.py b/aiosmtpd/handlers.py index 5e44f7ca..2d1b28fe 100644 --- a/aiosmtpd/handlers.py +++ b/aiosmtpd/handlers.py @@ -25,6 +25,7 @@ from public import public +from aiosmtpd import _get_or_new_eventloop from aiosmtpd.smtp import SMTP as SMTPServer from aiosmtpd.smtp import Envelope as SMTPEnvelope from aiosmtpd.smtp import Session as SMTPSession @@ -218,7 +219,7 @@ def __init__( loop: Optional[asyncio.AbstractEventLoop] = None, ): super().__init__(message_class) - self.loop = loop or asyncio.get_event_loop() + self.loop = loop or _get_or_new_eventloop() async def handle_DATA( self, server: SMTPServer, session: SMTPSession, envelope: SMTPEnvelope diff --git a/aiosmtpd/main.py b/aiosmtpd/main.py index 2366ae49..166484ca 100644 --- a/aiosmtpd/main.py +++ b/aiosmtpd/main.py @@ -1,7 +1,6 @@ # Copyright 2014-2021 The aiosmtpd Developers # SPDX-License-Identifier: Apache-2.0 -import asyncio import logging import os import signal @@ -16,7 +15,7 @@ from public import public -from aiosmtpd import __version__ +from aiosmtpd import __version__, _get_or_new_eventloop from aiosmtpd.smtp import DATA_SIZE_DEFAULT, SMTP try: @@ -252,7 +251,7 @@ def main(args: Optional[Sequence[str]] = None) -> None: logging.basicConfig(level=logging.ERROR) log = logging.getLogger("mail.log") - loop = asyncio.get_event_loop() + loop = _get_or_new_eventloop() if args.debug > 0: log.setLevel(logging.INFO) diff --git a/aiosmtpd/smtp.py b/aiosmtpd/smtp.py index 95181ea3..a977f751 100644 --- a/aiosmtpd/smtp.py +++ b/aiosmtpd/smtp.py @@ -34,7 +34,7 @@ import attr from public import public -from aiosmtpd import __version__ +from aiosmtpd import __version__, _get_or_new_eventloop from aiosmtpd.proxy_protocol import ProxyData, get_proxy @@ -208,7 +208,7 @@ def __init__(self) -> None: # unit test suite. In that case, this function is mocked to set the debug # level on the loop (as if PYTHONASYNCIODEBUG=1 were set). def make_loop() -> asyncio.AbstractEventLoop: - return asyncio.get_event_loop() + return _get_or_new_eventloop() @public diff --git a/aiosmtpd/tests/conftest.py b/aiosmtpd/tests/conftest.py index 6a8c3dd1..0c691031 100644 --- a/aiosmtpd/tests/conftest.py +++ b/aiosmtpd/tests/conftest.py @@ -5,6 +5,7 @@ import inspect import socket import ssl +import warnings from contextlib import suppress from functools import wraps from smtplib import SMTP as SMTPClient @@ -200,14 +201,20 @@ def getter(*args, **kwargs) -> Any: @pytest.fixture def temp_event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]: - default_loop = asyncio.get_event_loop() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + try: + default_loop = asyncio.get_event_loop() + except (DeprecationWarning, RuntimeError): + default_loop = None new_loop = asyncio.new_event_loop() asyncio.set_event_loop(new_loop) # yield new_loop # new_loop.close() - asyncio.set_event_loop(default_loop) + if default_loop is not None: + asyncio.set_event_loop(default_loop) @pytest.fixture diff --git a/aiosmtpd/tests/test_misc.py b/aiosmtpd/tests/test_misc.py new file mode 100644 index 00000000..94f1489a --- /dev/null +++ b/aiosmtpd/tests/test_misc.py @@ -0,0 +1,52 @@ +# Copyright 2014-2021 The aiosmtpd Developers +# SPDX-License-Identifier: Apache-2.0 + +"""Test other aspects of the server implementation.""" + +import asyncio +import warnings +from typing import Generator, Optional + +import pytest + +from aiosmtpd import _get_or_new_eventloop + + +@pytest.fixture(scope="module") +def close_existing_loop() -> Generator[Optional[asyncio.AbstractEventLoop], None, None]: + loop: Optional[asyncio.AbstractEventLoop] + with warnings.catch_warnings(): + warnings.filterwarnings("error") + try: + loop = asyncio.get_event_loop() + except (DeprecationWarning, RuntimeError): + loop = None + if loop: + loop.stop() + loop.close() + asyncio.set_event_loop(None) + yield loop + else: + yield None + + +class TestInit: + + def test_create_new_if_none(self, close_existing_loop): + old_loop = close_existing_loop + loop: Optional[asyncio.AbstractEventLoop] + loop = _get_or_new_eventloop() + assert loop is not None + assert loop is not old_loop + assert isinstance(loop, asyncio.AbstractEventLoop) + + def test_not_create_new_if_exist(self, close_existing_loop): + old_loop = close_existing_loop + loop: Optional[asyncio.AbstractEventLoop] + loop = asyncio.new_event_loop() + assert loop is not old_loop + asyncio.set_event_loop(loop) + ret_loop = _get_or_new_eventloop() + assert ret_loop is not old_loop + assert ret_loop == loop + assert ret_loop is loop diff --git a/examples/authenticated_relayer/server.py b/examples/authenticated_relayer/server.py index c44160c1..f00b6020 100644 --- a/examples/authenticated_relayer/server.py +++ b/examples/authenticated_relayer/server.py @@ -94,7 +94,8 @@ async def amain(): print(f"Please create {DB_AUTH} first using make_user_db.py") sys.exit(1) logging.basicConfig(level=logging.DEBUG) - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) loop.create_task(amain()) try: loop.run_forever() diff --git a/examples/basic/server.py b/examples/basic/server.py index 9f6f714e..fcb572a6 100644 --- a/examples/basic/server.py +++ b/examples/basic/server.py @@ -15,7 +15,8 @@ async def amain(loop): if __name__ == '__main__': logging.basicConfig(level=logging.DEBUG) - loop = asyncio.get_event_loop() + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) loop.create_task(amain(loop=loop)) try: loop.run_forever() diff --git a/pyproject.toml b/pyproject.toml index e067d36d..8731e605 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,8 @@ source = [ [tool.coverage.coverage_conditional_plugin.rules] # Here we specify our pragma rules: +py-lt-312 = "sys_version_info < (3, 12)" +py-lt-310 = "sys_version_info < (3, 10)" py-ge-38 = "sys_version_info >= (3, 8)" py-lt-38 = "sys_version_info < (3, 8)" py-gt-36 = "sys_version_info > (3, 6)"