1111from typing import Type
1212from typing import TypeVar
1313
14+ from .warning_types import PytestDeprecationWarning
15+
1416
1517class OutcomeException (BaseException ):
1618 """OutcomeException and its subclass instances indicate and contain info
@@ -192,7 +194,11 @@ def xfail(reason: str = "") -> NoReturn:
192194
193195
194196def importorskip (
195- modname : str , minversion : Optional [str ] = None , reason : Optional [str ] = None
197+ modname : str ,
198+ minversion : Optional [str ] = None ,
199+ reason : Optional [str ] = None ,
200+ * ,
201+ exc_type : Optional [Type [ImportError ]] = None ,
196202) -> Any :
197203 """Import and return the requested module ``modname``, or skip the
198204 current test if the module cannot be imported.
@@ -205,30 +211,81 @@ def importorskip(
205211 :param reason:
206212 If given, this reason is shown as the message when the module cannot
207213 be imported.
214+ :param exc_type:
215+ The exception that should be captured in order to skip modules.
216+ Must be :py:class:`ImportError` or a subclass.
217+
218+ If the module can be imported but raises :class:`ImportError`, pytest will
219+ issue a warning to the user, as often users expect the module not to be
220+ found (which would raise :class:`ModuleNotFoundError` instead).
221+
222+ This warning can be suppressed by passing ``exc_type=ImportError`` explicitly.
223+
224+ See :ref:`import-or-skip-import-error` for details.
225+
208226
209227 :returns:
210228 The imported module. This should be assigned to its canonical name.
211229
212230 Example::
213231
214232 docutils = pytest.importorskip("docutils")
233+
234+ .. versionadded:: 8.2
235+
236+ The ``exc_type`` parameter.
215237 """
216238 import warnings
217239
218240 __tracebackhide__ = True
219241 compile (modname , "" , "eval" ) # to catch syntaxerrors
220242
243+ # Until pytest 9.1, we will warn the user if we catch ImportError (instead of ModuleNotFoundError),
244+ # as this might be hiding an installation/environment problem, which is not usually what is intended
245+ # when using importorskip() (#11523).
246+ # In 9.1, to keep the function signature compatible, we just change the code below to:
247+ # 1. Use `exc_type = ModuleNotFoundError` if `exc_type` is not given.
248+ # 2. Remove `warn_on_import` and the warning handling.
249+ if exc_type is None :
250+ exc_type = ImportError
251+ warn_on_import_error = True
252+ else :
253+ warn_on_import_error = False
254+
255+ skipped : Optional [Skipped ] = None
256+ warning : Optional [Warning ] = None
257+
221258 with warnings .catch_warnings ():
222259 # Make sure to ignore ImportWarnings that might happen because
223260 # of existing directories with the same name we're trying to
224261 # import but without a __init__.py file.
225262 warnings .simplefilter ("ignore" )
263+
226264 try :
227265 __import__ (modname )
228- except ImportError as exc :
266+ except exc_type as exc :
267+ # Do not raise or issue warnings inside the catch_warnings() block.
229268 if reason is None :
230269 reason = f"could not import { modname !r} : { exc } "
231- raise Skipped (reason , allow_module_level = True ) from None
270+ skipped = Skipped (reason , allow_module_level = True )
271+
272+ if warn_on_import_error and not isinstance (exc , ModuleNotFoundError ):
273+ lines = [
274+ "" ,
275+ f"Module '{ modname } ' was found, but when imported by pytest it raised:" ,
276+ f" { exc !r} " ,
277+ "In pytest 9.1 this warning will become an error by default." ,
278+ "You can fix the underlying problem, or alternatively overwrite this behavior and silence this "
279+ "warning by passing exc_type=ImportError explicitly." ,
280+ "See https://docs.pytest.org/en/stable/deprecations.html#pytest-importorskip-default-behavior-regarding-importerror" ,
281+ ]
282+ warning = PytestDeprecationWarning ("\n " .join (lines ))
283+
284+ if warning :
285+ warnings .warn (warning , stacklevel = 2 )
286+ if skipped :
287+ raise skipped
288+
232289 mod = sys .modules [modname ]
233290 if minversion is None :
234291 return mod
0 commit comments