From 3a2621158c000878574a3f7127eddc1abbd553b8 Mon Sep 17 00:00:00 2001 From: shekhuverma Date: Tue, 16 Apr 2024 19:54:16 +0530 Subject: [PATCH 1/9] Changed importError to ModuleNotFoundError --- src/_pytest/outcomes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index e2a816f5850..980cfe6de62 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -225,7 +225,7 @@ def importorskip( warnings.simplefilter("ignore") try: __import__(modname) - except ImportError as exc: + except ModuleNotFoundError as exc: if reason is None: reason = f"could not import {modname!r}: {exc}" raise Skipped(reason, allow_module_level=True) from None From fd288e13ac8681fc7c61691748e858785302f0d9 Mon Sep 17 00:00:00 2001 From: shekhuverma Date: Fri, 19 Apr 2024 16:43:07 +0530 Subject: [PATCH 2/9] added testing for importorskip --- testing/test_runner.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/testing/test_runner.py b/testing/test_runner.py index 8cc496f7064..02cfbb2c5e8 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1,4 +1,5 @@ # mypy: allow-untyped-defs +import builtins from functools import partial import inspect import os @@ -7,6 +8,8 @@ import types from typing import Dict from typing import List +from typing import Mapping +from typing import Sequence from typing import Tuple from typing import Type @@ -762,6 +765,27 @@ def test_importorskip_imports_last_module_part() -> None: assert os.path == ospath +def test_importorskip_importError_Exception() -> None: + ## Mocking the import function to raise a importError + realimport = builtins.__import__ + + def myimport( + name: str, + globals: Mapping[str, object] | None = None, + locals: Mapping[str, object] | None = None, + fromlist: Sequence[str] = (), + level: int = 0, + ) -> types.ModuleType: + raise ImportError + + builtins.__import__ = myimport + + with pytest.raises(ImportError): + pytest.importorskip("abcdefghi") + + builtins.__import__ = realimport + + def test_importorskip_dev_module(monkeypatch) -> None: try: mod = types.ModuleType("mockmodule") From 543262c4c83cfaa125394db82fc5c9653e92c076 Mon Sep 17 00:00:00 2001 From: shekhuverma Date: Fri, 19 Apr 2024 19:26:57 +0530 Subject: [PATCH 3/9] added exc_types parameter in importorskip --- changelog/11523.improvement.rst | 1 + src/_pytest/outcomes.py | 23 +++++++++++++++++++++-- testing/test_runner.py | 33 ++++++++++++--------------------- 3 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 changelog/11523.improvement.rst diff --git a/changelog/11523.improvement.rst b/changelog/11523.improvement.rst new file mode 100644 index 00000000000..91cd6ccc2fc --- /dev/null +++ b/changelog/11523.improvement.rst @@ -0,0 +1 @@ +pytest.importorskip will now skip both ImportError and ModuleNotFoundError. diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 980cfe6de62..0e4e5f56660 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -11,6 +11,8 @@ from typing import Type from typing import TypeVar +from .warning_types import PytestDeprecationWarning + class OutcomeException(BaseException): """OutcomeException and its subclass instances indicate and contain info @@ -192,7 +194,10 @@ def xfail(reason: str = "") -> NoReturn: def importorskip( - modname: str, minversion: Optional[str] = None, reason: Optional[str] = None + modname: str, + minversion: Optional[str] = None, + reason: Optional[str] = None, + exc_type: Optional[Type[ImportError]] = None, ) -> Any: """Import and return the requested module ``modname``, or skip the current test if the module cannot be imported. @@ -205,6 +210,8 @@ def importorskip( :param reason: If given, this reason is shown as the message when the module cannot be imported. + :param exc_type: + If given, modules are skipped if this exception is raised. :returns: The imported module. This should be assigned to its canonical name. @@ -223,12 +230,24 @@ def importorskip( # of existing directories with the same name we're trying to # import but without a __init__.py file. warnings.simplefilter("ignore") + + if exc_type is None: + exc_type = ModuleNotFoundError + else: + exc_type = ImportError + warnings.warn( + PytestDeprecationWarning( + "The Default behaviour will change to ImportError in future", + ) + ) + try: __import__(modname) - except ModuleNotFoundError as exc: + except exc_type as exc: if reason is None: reason = f"could not import {modname!r}: {exc}" raise Skipped(reason, allow_module_level=True) from None + mod = sys.modules[modname] if minversion is None: return mod diff --git a/testing/test_runner.py b/testing/test_runner.py index 02cfbb2c5e8..fd8d3518722 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1,5 +1,4 @@ # mypy: allow-untyped-defs -import builtins from functools import partial import inspect import os @@ -8,8 +7,6 @@ import types from typing import Dict from typing import List -from typing import Mapping -from typing import Sequence from typing import Tuple from typing import Type @@ -22,6 +19,7 @@ from _pytest.monkeypatch import MonkeyPatch from _pytest.outcomes import OutcomeException from _pytest.pytester import Pytester +from _pytest.warning_types import PytestDeprecationWarning import pytest @@ -765,25 +763,18 @@ def test_importorskip_imports_last_module_part() -> None: assert os.path == ospath -def test_importorskip_importError_Exception() -> None: - ## Mocking the import function to raise a importError - realimport = builtins.__import__ - - def myimport( - name: str, - globals: Mapping[str, object] | None = None, - locals: Mapping[str, object] | None = None, - fromlist: Sequence[str] = (), - level: int = 0, - ) -> types.ModuleType: - raise ImportError - - builtins.__import__ = myimport - - with pytest.raises(ImportError): - pytest.importorskip("abcdefghi") +def test_importorskip_importError_warning(pytester: Pytester) -> None: + """ + importorskip() will only skip modules by ImportError as well as ModuleNotFoundError + will give warning when using ImportError + #11523 + """ + fn = pytester.makepyfile("raise ImportError('some specific problem')") + pytester.syspathinsert() - builtins.__import__ = realimport + with pytest.raises(pytest.skip.Exception): + with pytest.warns(PytestDeprecationWarning): + pytest.importorskip(fn.stem, exc_type=ImportError) def test_importorskip_dev_module(monkeypatch) -> None: From 9a9643d29e12f8d7f2153c0aa174f3ca75445c5c Mon Sep 17 00:00:00 2001 From: shekhuverma Date: Sat, 20 Apr 2024 12:43:16 +0530 Subject: [PATCH 4/9] Added warning and Test Cases --- src/_pytest/outcomes.py | 19 ++++++++++++------- testing/test_runner.py | 7 ++++++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 0e4e5f56660..8f19f29e157 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -232,20 +232,25 @@ def importorskip( warnings.simplefilter("ignore") if exc_type is None: - exc_type = ModuleNotFoundError - else: exc_type = ImportError - warnings.warn( - PytestDeprecationWarning( - "The Default behaviour will change to ImportError in future", - ) - ) + warn_on_import_error = True + else: + warn_on_import_error = False try: __import__(modname) except exc_type as exc: if reason is None: reason = f"could not import {modname!r}: {exc}" + if warn_on_import_error and type(exc) is ImportError: + warnings.warn( + PytestDeprecationWarning( + f"""pytest.importorskip() caught {exc},but this will change in a future pytest release + to only capture ModuleNotFoundError exceptions by default.\nTo overwrite the future + behavior and silence this warning, pass exc_type=ImportError explicitly.""" + ) + ) + raise Skipped(reason, allow_module_level=True) from None mod = sys.modules[modname] diff --git a/testing/test_runner.py b/testing/test_runner.py index fd8d3518722..f6ab5e4507b 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -774,7 +774,12 @@ def test_importorskip_importError_warning(pytester: Pytester) -> None: with pytest.raises(pytest.skip.Exception): with pytest.warns(PytestDeprecationWarning): - pytest.importorskip(fn.stem, exc_type=ImportError) + pytest.importorskip(fn.stem) + + +def test_importorskip_ModuleNotFoundError() -> None: + with pytest.raises(pytest.skip.Exception): + pytest.importorskip("abcdefgh") def test_importorskip_dev_module(monkeypatch) -> None: From 7de869d8c2b9fe06979faf82729fa07449e55763 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 20 Apr 2024 10:04:57 -0300 Subject: [PATCH 5/9] Improve tests and docs --- changelog/11523.improvement.rst | 6 ++- doc/en/deprecations.rst | 38 ++++++++++++++++ src/_pytest/outcomes.py | 64 ++++++++++++++++++++------- testing/test_runner.py | 78 ++++++++++++++++++++++++++------- 4 files changed, 154 insertions(+), 32 deletions(-) diff --git a/changelog/11523.improvement.rst b/changelog/11523.improvement.rst index 91cd6ccc2fc..f7d8ff89df6 100644 --- a/changelog/11523.improvement.rst +++ b/changelog/11523.improvement.rst @@ -1 +1,5 @@ -pytest.importorskip will now skip both ImportError and ModuleNotFoundError. +:func:`pytest.importorskip` will now issue a warning if the module could be found, but raised :class:`ImportError` instead of :class:`ModuleNotFoundError`. + +The warning can be suppressed by passing ``exc_type=ImportError`` to :func:`pytest.importorskip`. + +See :ref:`import-or-skip-import-error` for details. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index cd6d1e60aef..36b1e7d80bf 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -19,6 +19,44 @@ Below is a complete list of all pytest features which are considered deprecated. :class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters `. +.. _import-or-skip-import-error: + +``pytest.importorskip`` default behavior regarding :class:`ImportError` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 8.2 + +Traditionally :func:`pytest.importorskip` will capture :class:`ImportError`, with the original intent being to skip +tests where a dependent module is not installed, for example testing with different available dependencies. + +However some packages might be installed in the system, but are not importable due to +some other issue (e.g., a compilation error or a broken installation). In those cases :func:`pytest.importorskip` +would still silently skip the test, but more often than not users would like to see the unexpected +error so the underlying issue can be fixed. + +In ``8.2`` the ``exc_type`` parameter has been added, giving users the ability of passing :class:`ModuleNotFoundError` +to only skip tests only if the module cannot really be found, and not because of some other error. + +Catching only :class:`ModuleNotFoundError` by default (and let other errors propagate) would be the best solution, +however for backward compatibility, pytest will keep the existing behavior but raise an warning if: + +1. The captured exception is of type :class:`ImportError`, and: +2. The user does not pass ``exc_type`` explicitly. + +If the import attempt raises :class:`ModuleNotFoundError` (the usual case), then the module is skipped and no +warning is emitted. + +This will maintain the normal cases working the same way, while unexpected errors will now issue a warning. +Users can supress the warning by passing ``exc_type=ImportError`` explicitly. + +In ``9.0``, the warning will turn into an error, and in ``9.1`` :func:`pytest.importorskip` will only capture +:class:`ModuleNotFoundError` by default and no warnings will be issued anymore -- but users can still capture +:class:`ImportError` by passing it to ``exc_type``. + +This roadmap should then be as little disruptive as possible: the intended case will continue to work as normal, +and the exceptional cases will issue a warning, while providing users with a escape hatch when needed. + + .. _node-ctor-fspath-deprecation: ``fspath`` argument for Node constructors replaced with ``pathlib.Path`` diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 8f19f29e157..75fa1f086c2 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -211,7 +211,17 @@ def importorskip( If given, this reason is shown as the message when the module cannot be imported. :param exc_type: - If given, modules are skipped if this exception is raised. + The exception that should be captured in order to skip modules. + Must be :py:class:`ImportError` or a subclass. + + If the module can be imported but raises :class:`ImportError`, pytest will + issue a warning to the user, as often users expect the module not to be + found (which would raise :class:`ModuleNotFoundError` instead). + + This warning can be suppressed by passing ``exc_type=ImportError`` explicitly. + + See :ref:`import-or-skip-import-error` for details. + :returns: The imported module. This should be assigned to its canonical name. @@ -219,39 +229,61 @@ def importorskip( Example:: docutils = pytest.importorskip("docutils") + + .. versionadded:: 8.2 + + The ``exc_type`` parameter. """ import warnings __tracebackhide__ = True compile(modname, "", "eval") # to catch syntaxerrors + # Until pytest 9.1, we will warn the user if we catch ImportError (instead of ModuleNotFoundError), + # as this might be hiding an installation/environment problem, which is not usually what is intended + # when using importorskip() (#11523). + # In 9.1, to keep the function signature compatible, we just change the code below to: + # 1. Use `exc_type = ModuleNotFoundError` if `exc_type` is not given. + # 2. Remove `warn_on_import` and the warning handling. + if exc_type is None: + exc_type = ImportError + warn_on_import_error = True + else: + warn_on_import_error = False + + skipped: Optional[Skipped] = None + warning: Optional[Warning] = None + with warnings.catch_warnings(): # Make sure to ignore ImportWarnings that might happen because # of existing directories with the same name we're trying to # import but without a __init__.py file. warnings.simplefilter("ignore") - if exc_type is None: - exc_type = ImportError - warn_on_import_error = True - else: - warn_on_import_error = False - try: __import__(modname) except exc_type as exc: + # Do not raise or issue warnings inside the catch_warnings() block. if reason is None: reason = f"could not import {modname!r}: {exc}" + skipped = Skipped(reason, allow_module_level=True) + if warn_on_import_error and type(exc) is ImportError: - warnings.warn( - PytestDeprecationWarning( - f"""pytest.importorskip() caught {exc},but this will change in a future pytest release - to only capture ModuleNotFoundError exceptions by default.\nTo overwrite the future - behavior and silence this warning, pass exc_type=ImportError explicitly.""" - ) - ) - - raise Skipped(reason, allow_module_level=True) from None + lines = [ + "", + f"Module '{modname}' was found, but when imported by pytest it raised:", + f" {exc!r}", + "In pytest 9.1 this warning will become an error by default.", + "You can fix the underlying problem, or alternatively overwrite this behavior and silence this " + "warning by passing exc_type=ImportError explicitly.", + "See https://docs.pytest.org/en/stable/deprecations.html#pytest-importorskip-default-behavior-regarding-importerror", + ] + warning = PytestDeprecationWarning("\n".join(lines)) + + if warning: + warnings.warn(warning, stacklevel=2) + if skipped: + raise skipped mod = sys.modules[modname] if minversion is None: diff --git a/testing/test_runner.py b/testing/test_runner.py index 69ff27ee46d..6e034e94532 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -9,6 +9,7 @@ from typing import List from typing import Tuple from typing import Type +import warnings from _pytest import outcomes from _pytest import reports @@ -19,7 +20,6 @@ from _pytest.monkeypatch import MonkeyPatch from _pytest.outcomes import OutcomeException from _pytest.pytester import Pytester -from _pytest.warning_types import PytestDeprecationWarning import pytest @@ -763,23 +763,71 @@ def test_importorskip_imports_last_module_part() -> None: assert os.path == ospath -def test_importorskip_importError_warning(pytester: Pytester) -> None: - """ - importorskip() will only skip modules by ImportError as well as ModuleNotFoundError - will give warning when using ImportError - #11523 - """ - fn = pytester.makepyfile("raise ImportError('some specific problem')") - pytester.syspathinsert() +class TestImportOrSkipExcType: + """Tests for #11523.""" + + def test_no_warning(self) -> None: + # An attempt on a module which does not exist will raise ModuleNotFoundError, so it will + # be skipped normally and no warning will be issued. + with warnings.catch_warnings(record=True) as captured: + warnings.simplefilter("always") + + with pytest.raises(pytest.skip.Exception): + pytest.importorskip("TestImportOrSkipExcType_test_no_warning") + + assert captured == [] + + def test_import_error_with_warning(self, pytester: Pytester) -> None: + # Create a module which exists and can be imported, however it raises + # ImportError due to some other problem. In this case we will issue a warning + # about the future behavior change. + fn = pytester.makepyfile("raise ImportError('some specific problem')") + pytester.syspathinsert() + + with warnings.catch_warnings(record=True) as captured: + warnings.simplefilter("always") + + with pytest.raises(pytest.skip.Exception): + pytest.importorskip(fn.stem) + + [warning] = captured + assert warning.category is pytest.PytestDeprecationWarning + + def test_import_error_suppress_warning(self, pytester: Pytester) -> None: + # Same as test_import_error_with_warning, but we can suppress the warning + # by passing ImportError as exc_type. + fn = pytester.makepyfile("raise ImportError('some specific problem')") + pytester.syspathinsert() - with pytest.raises(pytest.skip.Exception): - with pytest.warns(PytestDeprecationWarning): - pytest.importorskip(fn.stem) + with warnings.catch_warnings(record=True) as captured: + warnings.simplefilter("always") + with pytest.raises(pytest.skip.Exception): + pytest.importorskip(fn.stem, exc_type=ImportError) -def test_importorskip_ModuleNotFoundError() -> None: - with pytest.raises(pytest.skip.Exception): - pytest.importorskip("abcdefgh") + assert captured == [] + + def test_warning_integration(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + def test_foo(): + pytest.importorskip("warning_integration_module") + """ + ) + pytester.makepyfile( + warning_integration_module=""" + raise ImportError("required library foobar not compiled properly") + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*Module 'warning_integration_module' was found, but when imported by pytest it raised:", + "* ImportError('required library foobar not compiled properly')", + "*1 skipped, 1 warning*", + ] + ) def test_importorskip_dev_module(monkeypatch) -> None: From 3ca329b968c2cf49a5c8cad8a58a3df5dc1c38b2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 20 Apr 2024 10:11:47 -0300 Subject: [PATCH 6/9] Improve deprecation docs --- doc/en/deprecations.rst | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 36b1e7d80bf..9b5a80e9520 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -27,15 +27,15 @@ Below is a complete list of all pytest features which are considered deprecated. .. deprecated:: 8.2 Traditionally :func:`pytest.importorskip` will capture :class:`ImportError`, with the original intent being to skip -tests where a dependent module is not installed, for example testing with different available dependencies. +tests where a dependent module is not installed, for example testing with different dependencies. However some packages might be installed in the system, but are not importable due to -some other issue (e.g., a compilation error or a broken installation). In those cases :func:`pytest.importorskip` +some other issue, for example, a compilation error or a broken installation. In those cases :func:`pytest.importorskip` would still silently skip the test, but more often than not users would like to see the unexpected error so the underlying issue can be fixed. In ``8.2`` the ``exc_type`` parameter has been added, giving users the ability of passing :class:`ModuleNotFoundError` -to only skip tests only if the module cannot really be found, and not because of some other error. +to skip tests only if the module cannot really be found, and not because of some other error. Catching only :class:`ModuleNotFoundError` by default (and let other errors propagate) would be the best solution, however for backward compatibility, pytest will keep the existing behavior but raise an warning if: @@ -46,16 +46,13 @@ however for backward compatibility, pytest will keep the existing behavior but r If the import attempt raises :class:`ModuleNotFoundError` (the usual case), then the module is skipped and no warning is emitted. -This will maintain the normal cases working the same way, while unexpected errors will now issue a warning. -Users can supress the warning by passing ``exc_type=ImportError`` explicitly. +This way, the usual cases will keep working the same way, while unexpected errors will now issue a warning, with +users being able to supress the warning by passing ``exc_type=ImportError`` explicitly. In ``9.0``, the warning will turn into an error, and in ``9.1`` :func:`pytest.importorskip` will only capture :class:`ModuleNotFoundError` by default and no warnings will be issued anymore -- but users can still capture :class:`ImportError` by passing it to ``exc_type``. -This roadmap should then be as little disruptive as possible: the intended case will continue to work as normal, -and the exceptional cases will issue a warning, while providing users with a escape hatch when needed. - .. _node-ctor-fspath-deprecation: From f14d78dbef8c682fc1195dfee45cfebce91d7ca9 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 20 Apr 2024 10:14:52 -0300 Subject: [PATCH 7/9] Change exc_type to kw only --- src/_pytest/outcomes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 75fa1f086c2..69822272538 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -197,6 +197,7 @@ def importorskip( modname: str, minversion: Optional[str] = None, reason: Optional[str] = None, + *, exc_type: Optional[Type[ImportError]] = None, ) -> Any: """Import and return the requested module ``modname``, or skip the From b59336ac47b775fcc62f7a79e2e60190213f36a0 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 25 Apr 2024 17:57:04 -0300 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Florian Bruhin --- doc/en/deprecations.rst | 2 +- src/_pytest/outcomes.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 9b5a80e9520..5ac93f15144 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -37,7 +37,7 @@ error so the underlying issue can be fixed. In ``8.2`` the ``exc_type`` parameter has been added, giving users the ability of passing :class:`ModuleNotFoundError` to skip tests only if the module cannot really be found, and not because of some other error. -Catching only :class:`ModuleNotFoundError` by default (and let other errors propagate) would be the best solution, +Catching only :class:`ModuleNotFoundError` by default (and letting other errors propagate) would be the best solution, however for backward compatibility, pytest will keep the existing behavior but raise an warning if: 1. The captured exception is of type :class:`ImportError`, and: diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 69822272538..8749141aa9e 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -269,7 +269,7 @@ def importorskip( reason = f"could not import {modname!r}: {exc}" skipped = Skipped(reason, allow_module_level=True) - if warn_on_import_error and type(exc) is ImportError: + if warn_on_import_error and not issubclass(exc, ModuleNotFoundError): lines = [ "", f"Module '{modname}' was found, but when imported by pytest it raised:", From 76fd0e5a5ae45d166d9c7ca9d0d71199630904d3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 25 Apr 2024 20:59:41 -0300 Subject: [PATCH 9/9] Fix check --- src/_pytest/outcomes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 8749141aa9e..76d94accd0d 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -269,7 +269,7 @@ def importorskip( reason = f"could not import {modname!r}: {exc}" skipped = Skipped(reason, allow_module_level=True) - if warn_on_import_error and not issubclass(exc, ModuleNotFoundError): + if warn_on_import_error and not isinstance(exc, ModuleNotFoundError): lines = [ "", f"Module '{modname}' was found, but when imported by pytest it raised:",