Skip to content

Commit aa8706e

Browse files
committed
Fix tox for Python >= 3.12
Fix import error: ``` py312-cov: commands[2]> pytest --cov --cov-report=xml --cov-report=html --cov-report=term --tb=short ImportError while loading conftest '/home/rominf/dev/aiosmtpd/aiosmtpd/tests/conftest.py'. aiosmtpd/tests/conftest.py:15: in <module> from pkg_resources import resource_filename E ModuleNotFoundError: No module named 'pkg_resources' ``` by migrating to `importlib.resources`: https://importlib-resources.readthedocs.io/en/latest/migration.html#pkg-resources-resource-filename
1 parent abadbd4 commit aa8706e

File tree

5 files changed

+49
-25
lines changed

5 files changed

+49
-25
lines changed

aiosmtpd/tests/conftest.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,28 @@
55
import inspect
66
import socket
77
import ssl
8+
import sys
89
import warnings
910
from contextlib import suppress
1011
from functools import wraps
12+
from pathlib import Path
1113
from smtplib import SMTP as SMTPClient
1214
from typing import Any, Callable, Generator, NamedTuple, Optional, Type, TypeVar
1315

1416
import pytest
15-
from pkg_resources import resource_filename
1617
from pytest_mock import MockFixture
1718

1819
from aiosmtpd.controller import Controller
1920
from aiosmtpd.handlers import Sink
2021

22+
# `importlib.resources.files` was added in Python 3.9:
23+
# https://docs.python.org/3/library/importlib.resources.html#importlib.resources.files
24+
# Use backport https://github.com/python/importlib_resources on Python 3.8.
25+
if sys.version_info[:2] == (3, 8):
26+
import importlib_resources
27+
else:
28+
import importlib.resources as importlib_resources
29+
2130
try:
2231
from asyncio.proactor_events import _ProactorBasePipeTransport
2332

@@ -32,8 +41,6 @@
3241
"handler_data",
3342
"Global",
3443
"AUTOSTOP_DELAY",
35-
"SERVER_CRT",
36-
"SERVER_KEY",
3744
]
3845

3946

@@ -73,8 +80,6 @@ def set_addr_from(cls, contr: Controller):
7380
# If less than 1.0, might cause intermittent error if test system
7481
# is too busy/overloaded.
7582
AUTOSTOP_DELAY = 1.5
76-
SERVER_CRT = resource_filename("aiosmtpd.tests.certs", "server.crt")
77-
SERVER_KEY = resource_filename("aiosmtpd.tests.certs", "server.key")
7883

7984
# endregion
8085

@@ -99,6 +104,22 @@ def cache_fqdn(session_mocker: MockFixture):
99104
# region #### Common Fixtures #########################################################
100105

101106

107+
def _server_resource(name: str, /) -> Generator[Path, None, None]:
108+
ref = importlib_resources.files("aiosmtpd.tests.certs") / name
109+
with importlib_resources.as_file(ref) as path:
110+
yield path
111+
112+
113+
@pytest.fixture(scope="session")
114+
def server_crt() -> Generator[Path, None, None]:
115+
yield from _server_resource("server.crt")
116+
117+
118+
@pytest.fixture(scope="session")
119+
def server_key() -> Generator[Path, None, None]:
120+
yield from _server_resource("server.key")
121+
122+
102123
@pytest.fixture
103124
def get_controller(request: pytest.FixtureRequest) -> Callable[..., Controller]:
104125
"""
@@ -315,25 +336,25 @@ def client(request: pytest.FixtureRequest) -> Generator[SMTPClient, None, None]:
315336

316337

317338
@pytest.fixture
318-
def ssl_context_server() -> ssl.SSLContext:
339+
def ssl_context_server(server_crt: Path, server_key: Path) -> ssl.SSLContext:
319340
"""
320341
Provides a server-side SSL Context
321342
"""
322343
context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
323344
context.check_hostname = False
324-
context.load_cert_chain(SERVER_CRT, SERVER_KEY)
345+
context.load_cert_chain(server_crt, server_key)
325346
#
326347
return context
327348

328349

329350
@pytest.fixture
330-
def ssl_context_client() -> ssl.SSLContext:
351+
def ssl_context_client(server_crt: Path) -> ssl.SSLContext:
331352
"""
332353
Provides a client-side SSL Context
333354
"""
334355
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
335356
context.check_hostname = False
336-
context.load_verify_locations(SERVER_CRT)
357+
context.load_verify_locations(server_crt)
337358
#
338359
return context
339360

aiosmtpd/tests/test_main.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import time
1010
from contextlib import contextmanager
1111
from multiprocessing.synchronize import Event as MP_Event
12+
from pathlib import Path
1213
from smtplib import SMTP as SMTPClient
1314
from smtplib import SMTP_SSL
1415
from typing import Generator
@@ -21,7 +22,7 @@
2122
from aiosmtpd.main import main, parseargs
2223
from aiosmtpd.testing.helpers import catchup_delay
2324
from aiosmtpd.testing.statuscodes import SMTP_STATUS_CODES as S
24-
from aiosmtpd.tests.conftest import AUTOSTOP_DELAY, SERVER_CRT, SERVER_KEY
25+
from aiosmtpd.tests.conftest import AUTOSTOP_DELAY
2526

2627
try:
2728
import pwd
@@ -199,24 +200,24 @@ def test_debug_3(self):
199200

200201
@pytest.mark.skipif(sys.platform == "darwin", reason="No idea why these are failing")
201202
class TestMainByWatcher:
202-
def test_tls(self, temp_event_loop):
203+
def test_tls(self, temp_event_loop, server_crt: Path, server_key: Path):
203204
with watcher_process(watch_for_tls) as retq:
204205
temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop)
205-
main_n("--tlscert", str(SERVER_CRT), "--tlskey", str(SERVER_KEY))
206+
main_n("--tlscert", str(server_crt), "--tlskey", str(server_key))
206207
catchup_delay()
207208
has_starttls = retq.get()
208209
assert has_starttls is True
209210
require_tls = retq.get()
210211
assert require_tls is True
211212

212-
def test_tls_noreq(self, temp_event_loop):
213+
def test_tls_noreq(self, temp_event_loop, server_crt: Path, server_key: Path):
213214
with watcher_process(watch_for_tls) as retq:
214215
temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop)
215216
main_n(
216217
"--tlscert",
217-
str(SERVER_CRT),
218+
str(server_crt),
218219
"--tlskey",
219-
str(SERVER_KEY),
220+
str(server_key),
220221
"--no-requiretls",
221222
)
222223
catchup_delay()
@@ -225,10 +226,10 @@ def test_tls_noreq(self, temp_event_loop):
225226
require_tls = retq.get()
226227
assert require_tls is False
227228

228-
def test_smtps(self, temp_event_loop):
229+
def test_smtps(self, temp_event_loop, server_crt: Path, server_key: Path):
229230
with watcher_process(watch_for_smtps) as retq:
230231
temp_event_loop.call_later(AUTOSTOP_DELAY, temp_event_loop.stop)
231-
main_n("--smtpscert", str(SERVER_CRT), "--smtpskey", str(SERVER_KEY))
232+
main_n("--smtpscert", str(server_crt), "--smtpskey", str(server_key))
232233
catchup_delay()
233234
has_smtps = retq.get()
234235
assert has_smtps is True
@@ -335,19 +336,21 @@ def test_norequiretls(self, capsys, mocker):
335336
assert args.requiretls is False
336337

337338
@pytest.mark.parametrize(
338-
("certfile", "keyfile", "expect"),
339+
("certfile_present", "keyfile_present", "expect"),
339340
[
340-
("x", "x", "Cert file x not found"),
341-
(SERVER_CRT, "x", "Key file x not found"),
342-
("x", SERVER_KEY, "Cert file x not found"),
341+
(False, False, "Cert file x not found"),
342+
(True, False, "Key file x not found"),
343+
(False, True, "Cert file x not found"),
343344
],
344345
ids=["x-x", "cert-x", "x-key"],
345346
)
346347
@pytest.mark.parametrize("meth", ["smtps", "tls"])
347-
def test_ssl_files_err(self, capsys, mocker, meth, certfile, keyfile, expect):
348+
def test_ssl_files_err(self, capsys, mocker, meth, certfile_present, keyfile_present, expect, request: pytest.FixtureRequest):
349+
certfile = request.getfixturevalue("server_crt") if certfile_present else "x"
350+
keyfile = request.getfixturevalue("server_key") if keyfile_present else "x"
348351
mocker.patch("aiosmtpd.main.PROGRAM", "smtpd")
349352
with pytest.raises(SystemExit) as exc:
350-
parseargs((f"--{meth}cert", certfile, f"--{meth}key", keyfile))
353+
parseargs((f"--{meth}cert", str(certfile), f"--{meth}key", str(keyfile)))
351354
assert exc.value.code == 2
352355
assert expect in capsys.readouterr().err
353356

pytest.ini

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ addopts =
1313
asyncio_mode = auto
1414
filterwarnings =
1515
error
16-
# TODO: Replace pkg_resources
17-
ignore:pkg_resources is deprecated as an API:DeprecationWarning
1816
# TODO: Fix resource warnings
1917
ignore:unclosed transport:ResourceWarning
2018
ignore:unclosed <socket.socket:ResourceWarning

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
atpublic==5.0
22
attrs==24.2.0
33
coverage==7.6.1
4+
importlib_resources;python_version<"3.9"
45
pytest==8.3.2
56
pytest-asyncio==0.24.0
67
pytest-cov==5.0.0

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ usedevelop = True
2121
deps =
2222
bandit
2323
colorama
24+
importlib_resources;python_version<"3.9"
2425
packaging
2526
pytest >= 6.0 # Require >= 6.0 for pyproject.toml support (PEP 517)
2627
pytest-mock

0 commit comments

Comments
 (0)