From ce2bc5744dd712e3facc0a3fe51f51318e5b1842 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 18 Jan 2026 11:20:18 +0100 Subject: [PATCH 1/4] fix: show correct program name in argparse help/errors Display 'pytest', 'python -m pytest', or 'pytest.main()' based on how pytest was invoked, fixing confusing error messages when calling pytest.main() programmatically. Fixes #1764 Co-authored-by: Cursor AI Co-authored-by: Anthropic Claude Opus 4 --- changelog/1764.improvement.rst | 1 + src/_pytest/config/__init__.py | 41 +++++++++++++++-- testing/test_config.py | 81 ++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 changelog/1764.improvement.rst diff --git a/changelog/1764.improvement.rst b/changelog/1764.improvement.rst new file mode 100644 index 00000000000..0a25f39faf9 --- /dev/null +++ b/changelog/1764.improvement.rst @@ -0,0 +1 @@ +Improved argparse program name to show ``pytest``, ``python -m pytest``, or ``pytest.main()`` based on how pytest was invoked, making help and error messages clearer. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 44d606b00a4..e4404570c68 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -172,9 +172,33 @@ def print_usage_error(e: UsageError, file: TextIO) -> None: tw.line(f"ERROR: {msg}\n", red=True) +def _get_prog_name( + *, invoked_from_console: bool, _argv: list[str] | None = None +) -> str: + """Determine the appropriate program name for argparse based on invocation context. + + :param invoked_from_console: Whether pytest was invoked from the CLI entry point. + :param _argv: Optional argv list for testing; defaults to sys.argv. + :returns: The program name to display in help and error messages. + """ + if not invoked_from_console: + # Called programmatically via pytest.main() + return "pytest.main()" + + # Called from CLI - check if it's `python -m pytest` or direct `pytest` + argv = sys.argv if _argv is None else _argv + argv0 = argv[0] if argv else "" + # When running as `python -m pytest`, argv[0] is the path to __main__.py + if os.path.basename(argv0) == "__main__.py": + return "python -m pytest" + return "pytest" + + def main( args: list[str] | os.PathLike[str] | None = None, plugins: Sequence[str | _PluggyPlugin] | None = None, + *, + _invoked_from_console: bool = False, ) -> int | ExitCode: """Perform an in-process test run. @@ -194,11 +218,13 @@ def main( sys.stdout.write(f"pytest {__version__}\n") return ExitCode.OK + prog = _get_prog_name(invoked_from_console=_invoked_from_console) + old_pytest_version = os.environ.get("PYTEST_VERSION") try: os.environ["PYTEST_VERSION"] = __version__ try: - config = _prepareconfig(new_args, plugins) + config = _prepareconfig(new_args, plugins, prog=prog) except ConftestImportFailure as e: print_conftest_import_error(e, file=sys.stderr) return ExitCode.USAGE_ERROR @@ -228,7 +254,7 @@ def console_main() -> int: """ # https://docs.python.org/3/library/signal.html#note-on-sigpipe try: - code = main() + code = main(_invoked_from_console=True) sys.stdout.flush() return code except BrokenPipeError: @@ -314,6 +340,8 @@ def directory_arg(path: str, optname: str) -> str: def get_config( args: Iterable[str] | None = None, plugins: Sequence[str | _PluggyPlugin] | None = None, + *, + prog: str | None = None, ) -> Config: # Subsequent calls to main will create a fresh instance. pluginmanager = PytestPluginManager() @@ -322,7 +350,7 @@ def get_config( plugins=plugins, dir=pathlib.Path.cwd(), ) - config = Config(pluginmanager, invocation_params=invocation_params) + config = Config(pluginmanager, invocation_params=invocation_params, prog=prog) if invocation_params.args: # Handle any "-p no:plugin" args. @@ -348,6 +376,8 @@ def get_plugin_manager() -> PytestPluginManager: def _prepareconfig( args: list[str] | os.PathLike[str], plugins: Sequence[str | _PluggyPlugin] | None = None, + *, + prog: str | None = None, ) -> Config: if isinstance(args, os.PathLike): args = [os.fspath(args)] @@ -357,7 +387,7 @@ def _prepareconfig( ) raise TypeError(msg.format(args, type(args))) - initial_config = get_config(args, plugins) + initial_config = get_config(args, plugins, prog=prog) pluginmanager = initial_config.pluginmanager try: if plugins: @@ -1065,6 +1095,7 @@ def __init__( pluginmanager: PytestPluginManager, *, invocation_params: InvocationParams | None = None, + prog: str | None = None, ) -> None: if invocation_params is None: invocation_params = self.InvocationParams( @@ -1088,6 +1119,8 @@ def __init__( processopt=self._processopt, _ispytest=True, ) + if prog is not None: + self._parser.prog = prog self.pluginmanager = pluginmanager """The plugin manager handles plugin registration and hook invocation. diff --git a/testing/test_config.py b/testing/test_config.py index 8026c108db0..0bae328c556 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -14,6 +14,7 @@ import _pytest._code from _pytest.config import _get_plugin_specs_as_list +from _pytest.config import _get_prog_name from _pytest.config import _iter_rewritable_modules from _pytest.config import _strtobool from _pytest.config import Config @@ -3082,3 +3083,83 @@ def test(): result = pytester.runpytest() assert result.ret == 0 + + +class TestProgName: + """Test program name display in help and error messages (issue #1764).""" + + def test_get_prog_name_programmatic_invocation(self) -> None: + """When invoked programmatically, prog should be 'pytest.main()'.""" + # Regardless of what argv[0] is, programmatic invocation should + # always show pytest.main() + assert ( + _get_prog_name(invoked_from_console=False, _argv=["setup.py", "test"]) + == "pytest.main()" + ) + assert ( + _get_prog_name(invoked_from_console=False, _argv=["my_script.py"]) + == "pytest.main()" + ) + + def test_get_prog_name_console_pytest(self) -> None: + """When invoked via 'pytest' CLI, prog should be 'pytest'.""" + assert ( + _get_prog_name( + invoked_from_console=True, _argv=["/usr/bin/pytest", "--help"] + ) + == "pytest" + ) + assert ( + _get_prog_name(invoked_from_console=True, _argv=["pytest", "-v"]) + == "pytest" + ) + + def test_get_prog_name_console_python_m_pytest(self) -> None: + """When invoked via 'python -m pytest', prog should be 'python -m pytest'.""" + # When running as python -m pytest, argv[0] is the path to __main__.py + assert ( + _get_prog_name( + invoked_from_console=True, + _argv=["/path/to/site-packages/pytest/__main__.py", "--help"], + ) + == "python -m pytest" + ) + assert ( + _get_prog_name(invoked_from_console=True, _argv=["__main__.py", "-v"]) + == "python -m pytest" + ) + + def test_get_prog_name_empty_argv(self) -> None: + """When argv is empty, should handle gracefully.""" + # Empty argv with console invocation should default to pytest + assert _get_prog_name(invoked_from_console=True, _argv=[]) == "pytest" + # Empty argv with programmatic invocation should show pytest.main() + assert _get_prog_name(invoked_from_console=False, _argv=[]) == "pytest.main()" + + def test_prog_in_error_message_programmatic(self, pytester: Pytester) -> None: + """Error messages should show 'pytest.main()' when called programmatically. + + runpytest_inprocess calls pytest.main() directly, so it should show + pytest.main() as the program name. + """ + result = pytester.runpytest_inprocess("--invalid-option-xyz") + result.stderr.fnmatch_lines(["*pytest.main(): error:*invalid-option-xyz*"]) + + def test_prog_in_error_message_cli(self, pytester: Pytester) -> None: + """Error messages should show 'python -m pytest' when called from CLI subprocess. + + runpytest_subprocess runs pytest via 'python -m pytest', so it should + show 'python -m pytest' as the program name. + """ + result = pytester.runpytest_subprocess("--invalid-option-xyz") + result.stderr.fnmatch_lines(["*python -m pytest: error:*invalid-option-xyz*"]) + + def test_prog_in_usage_programmatic(self, pytester: Pytester) -> None: + """Usage line should show 'pytest.main()' when called programmatically.""" + result = pytester.runpytest_inprocess("--help") + result.stdout.fnmatch_lines(["usage: pytest.main() *"]) + + def test_prog_in_usage_cli(self, pytester: Pytester) -> None: + """Usage line should show 'python -m pytest' when called from CLI subprocess.""" + result = pytester.runpytest_subprocess("--help") + result.stdout.fnmatch_lines(["usage: python -m pytest *"]) From f3051d2b763082b92253a8d84a8616d0223aec47 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 24 May 2026 09:15:16 +0200 Subject: [PATCH 2/4] refactor: address review feedback on prog name implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract _main() so main() stays a clean public API without internal parameters leaking into autodoc - _get_prog_name() now takes a plain argv: Sequence[str] — the invoked_from_console boolean is gone; console_main resolves the prog name from sys.argv, main() always passes "pytest.main()" - Pass prog through Parser/PytestArgumentParser constructors instead of setting the attribute after construction - Simplify tests to match the new single-responsibility _get_prog_name Co-authored-by: Cursor AI Co-authored-by: Anthropic Claude Opus 4 --- src/_pytest/config/__init__.py | 39 +++++++++++------------- src/_pytest/config/argparsing.py | 6 +++- testing/test_config.py | 51 +++++++------------------------- 3 files changed, 32 insertions(+), 64 deletions(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index e4404570c68..1f58e6fccc5 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -172,23 +172,14 @@ def print_usage_error(e: UsageError, file: TextIO) -> None: tw.line(f"ERROR: {msg}\n", red=True) -def _get_prog_name( - *, invoked_from_console: bool, _argv: list[str] | None = None -) -> str: - """Determine the appropriate program name for argparse based on invocation context. - - :param invoked_from_console: Whether pytest was invoked from the CLI entry point. - :param _argv: Optional argv list for testing; defaults to sys.argv. - :returns: The program name to display in help and error messages. - """ - if not invoked_from_console: - # Called programmatically via pytest.main() - return "pytest.main()" +def _get_prog_name(argv: Sequence[str]) -> str: + """Determine the CLI program name from the argument vector. - # Called from CLI - check if it's `python -m pytest` or direct `pytest` - argv = sys.argv if _argv is None else _argv + :param argv: The argument vector (typically ``sys.argv``). + :returns: ``"python -m pytest"`` when invoked via ``python -m``, + ``"pytest"`` otherwise. + """ argv0 = argv[0] if argv else "" - # When running as `python -m pytest`, argv[0] is the path to __main__.py if os.path.basename(argv0) == "__main__.py": return "python -m pytest" return "pytest" @@ -197,8 +188,6 @@ def _get_prog_name( def main( args: list[str] | os.PathLike[str] | None = None, plugins: Sequence[str | _PluggyPlugin] | None = None, - *, - _invoked_from_console: bool = False, ) -> int | ExitCode: """Perform an in-process test run. @@ -209,6 +198,15 @@ def main( :returns: An exit code. """ + return _main(args=args, plugins=plugins, prog="pytest.main()") + + +def _main( + *, + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, + prog: str, +) -> int | ExitCode: # Handle a single `--version`/`-V` argument early to avoid starting up the entire pytest infrastructure. new_args = sys.argv[1:] if args is None else args if ( @@ -218,8 +216,6 @@ def main( sys.stdout.write(f"pytest {__version__}\n") return ExitCode.OK - prog = _get_prog_name(invoked_from_console=_invoked_from_console) - old_pytest_version = os.environ.get("PYTEST_VERSION") try: os.environ["PYTEST_VERSION"] = __version__ @@ -254,7 +250,7 @@ def console_main() -> int: """ # https://docs.python.org/3/library/signal.html#note-on-sigpipe try: - code = main(_invoked_from_console=True) + code = _main(prog=_get_prog_name(sys.argv)) sys.stdout.flush() return code except BrokenPipeError: @@ -1117,10 +1113,9 @@ def __init__( self._parser = Parser( usage=f"%(prog)s [options] [{FILE_OR_DIR}] [{FILE_OR_DIR}] [...]", processopt=self._processopt, + prog=prog, _ispytest=True, ) - if prog is not None: - self._parser.prog = prog self.pluginmanager = pluginmanager """The plugin manager handles plugin registration and hook invocation. diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index f2ec53374af..f70e27614ef 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -34,6 +34,7 @@ def __init__( usage: str | None = None, processopt: Callable[[Argument], None] | None = None, *, + prog: str | None = None, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) @@ -42,7 +43,7 @@ def __init__( self._processopt = processopt self.extra_info: dict[str, Any] = {} - self.optparser = PytestArgumentParser(usage, self.extra_info) + self.optparser = PytestArgumentParser(usage, self.extra_info, prog=prog) anonymous_arggroup = self.optparser.add_argument_group("Custom options") self._anonymous = OptionGroup( anonymous_arggroup, "_anonymous", self, _ispytest=True @@ -383,9 +384,12 @@ def __init__( self, usage: str | None, extra_info: dict[str, str], + *, + prog: str | None = None, ) -> None: super().__init__( usage=usage, + prog=prog, add_help=False, formatter_class=DropShorterLongHelpFormatter, allow_abbrev=False, diff --git a/testing/test_config.py b/testing/test_config.py index 0bae328c556..8d8328ac4cd 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -3088,53 +3088,22 @@ def test(): class TestProgName: """Test program name display in help and error messages (issue #1764).""" - def test_get_prog_name_programmatic_invocation(self) -> None: - """When invoked programmatically, prog should be 'pytest.main()'.""" - # Regardless of what argv[0] is, programmatic invocation should - # always show pytest.main() - assert ( - _get_prog_name(invoked_from_console=False, _argv=["setup.py", "test"]) - == "pytest.main()" - ) - assert ( - _get_prog_name(invoked_from_console=False, _argv=["my_script.py"]) - == "pytest.main()" - ) - - def test_get_prog_name_console_pytest(self) -> None: - """When invoked via 'pytest' CLI, prog should be 'pytest'.""" - assert ( - _get_prog_name( - invoked_from_console=True, _argv=["/usr/bin/pytest", "--help"] - ) - == "pytest" - ) - assert ( - _get_prog_name(invoked_from_console=True, _argv=["pytest", "-v"]) - == "pytest" - ) + def test_get_prog_name_direct_pytest(self) -> None: + """When argv[0] is a pytest entry point, prog should be 'pytest'.""" + assert _get_prog_name(["/usr/bin/pytest", "--help"]) == "pytest" + assert _get_prog_name(["pytest", "-v"]) == "pytest" - def test_get_prog_name_console_python_m_pytest(self) -> None: - """When invoked via 'python -m pytest', prog should be 'python -m pytest'.""" - # When running as python -m pytest, argv[0] is the path to __main__.py - assert ( - _get_prog_name( - invoked_from_console=True, - _argv=["/path/to/site-packages/pytest/__main__.py", "--help"], - ) - == "python -m pytest" - ) + def test_get_prog_name_python_m_pytest(self) -> None: + """When argv[0] is __main__.py, prog should be 'python -m pytest'.""" assert ( - _get_prog_name(invoked_from_console=True, _argv=["__main__.py", "-v"]) + _get_prog_name(["/path/to/site-packages/pytest/__main__.py", "--help"]) == "python -m pytest" ) + assert _get_prog_name(["__main__.py", "-v"]) == "python -m pytest" def test_get_prog_name_empty_argv(self) -> None: - """When argv is empty, should handle gracefully.""" - # Empty argv with console invocation should default to pytest - assert _get_prog_name(invoked_from_console=True, _argv=[]) == "pytest" - # Empty argv with programmatic invocation should show pytest.main() - assert _get_prog_name(invoked_from_console=False, _argv=[]) == "pytest.main()" + """When argv is empty, should default to 'pytest'.""" + assert _get_prog_name([]) == "pytest" def test_prog_in_error_message_programmatic(self, pytester: Pytester) -> None: """Error messages should show 'pytest.main()' when called programmatically. From 438c01d8b0d4d42742aa9bc9ee1fef372fe242e6 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Sun, 24 May 2026 10:14:57 +0200 Subject: [PATCH 3/4] deprecate: pytest.console_main() for removal in pytest 10 Make the real implementation _console_main() private. The public console_main() now emits PytestRemovedIn10Warning and delegates. Entry points (pyproject.toml) and __main__.py call _console_main directly so normal CLI usage is unaffected. Co-authored-by: Cursor AI Co-authored-by: Anthropic Claude Opus 4 --- changelog/1764.deprecation.rst | 2 ++ doc/en/deprecations.rst | 25 +++++++++++++++++++++++++ pyproject.toml | 4 ++-- src/_pytest/config/__init__.py | 21 ++++++++++++++++++--- src/_pytest/deprecated.py | 6 ++++++ src/pytest/__main__.py | 4 ++-- testing/test_config.py | 10 ++++++++++ 7 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 changelog/1764.deprecation.rst diff --git a/changelog/1764.deprecation.rst b/changelog/1764.deprecation.rst new file mode 100644 index 00000000000..7e57c17b62a --- /dev/null +++ b/changelog/1764.deprecation.rst @@ -0,0 +1,2 @@ +:func:`pytest.console_main` is now deprecated and will be removed in pytest 10. +It was never intended for programmatic use; use :func:`pytest.main` instead. diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 21464a6939e..6668e7393f3 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -42,6 +42,31 @@ node-based matching instead of fragile string prefix matching. In pytest 10, the ``baseid`` and ``nodeid`` string parameters will be removed. +.. _console-main: + +``pytest.console_main()`` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. deprecated:: 9.1 + +:func:`pytest.console_main` is deprecated and will be removed in pytest 10. + +This function is the CLI entry point used internally by the ``pytest`` console script +and ``python -m pytest``. It was never intended for programmatic use, and exposing it +in the public API led to confusion with :func:`pytest.main`, which is the correct way +to invoke pytest from Python code. + +If you are calling ``pytest.console_main()`` in your code, replace it with :func:`pytest.main`: + +.. code-block:: python + + # Deprecated + pytest.console_main() + + # Use this instead + exit_code = pytest.main() + + .. _pastebin-deprecated: The ``--pastebin`` option diff --git a/pyproject.toml b/pyproject.toml index 83d367d7024..886c968e8ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,8 +69,8 @@ urls.Funding = "https://docs.pytest.org/en/stable/sponsor.html" urls.Homepage = "https://docs.pytest.org/en/latest/" urls.Source = "https://github.com/pytest-dev/pytest" urls.Tracker = "https://github.com/pytest-dev/pytest/issues" -scripts."py.test" = "pytest:console_main" -scripts.pytest = "pytest:console_main" +scripts."py.test" = "_pytest.config:_console_main" +scripts.pytest = "_pytest.config:_console_main" [tool.setuptools.package-data] "_pytest" = [ diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 1f58e6fccc5..bc75c1e16fc 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -243,10 +243,10 @@ def _main( os.environ["PYTEST_VERSION"] = old_pytest_version -def console_main() -> int: - """The CLI entry point of pytest. +def _console_main() -> int: + """The CLI entry point of pytest (internal). - This function is not meant for programmable use; use `main()` instead. + This is the real implementation used by entry points and ``__main__.py``. """ # https://docs.python.org/3/library/signal.html#note-on-sigpipe try: @@ -261,6 +261,21 @@ def console_main() -> int: return 1 # Python exits with error code 1 on EPIPE +def console_main() -> int: + """The CLI entry point of pytest. + + .. deprecated:: 9.1 + This function is slated for removal in pytest 10. + It is not meant for programmable use; use :func:`pytest.main` instead. + """ + import warnings + + from _pytest.deprecated import CONSOLE_MAIN + + warnings.warn(CONSOLE_MAIN, stacklevel=2) + return _console_main() + + class cmdline: # compatibility namespace main = staticmethod(main) diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 8d57a74520b..70640f7efb1 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -70,6 +70,12 @@ "See https://docs.pytest.org/en/stable/deprecations.html#parametrize-iterators", ) +CONSOLE_MAIN = PytestRemovedIn10Warning( + "pytest.console_main() is deprecated and will be removed in pytest 10.\n" + "It was never intended for programmatic use; use pytest.main() instead.\n" + "See https://docs.pytest.org/en/stable/deprecations.html#console-main" +) + CONFIG_INICFG = PytestRemovedIn10Warning( "config.inicfg is deprecated, use config.getini() to access configuration values instead.\n" "See https://docs.pytest.org/en/stable/deprecations.html#config-inicfg" diff --git a/src/pytest/__main__.py b/src/pytest/__main__.py index cccab5d57b8..9912fa0b5ac 100644 --- a/src/pytest/__main__.py +++ b/src/pytest/__main__.py @@ -2,8 +2,8 @@ from __future__ import annotations -import pytest +from _pytest.config import _console_main if __name__ == "__main__": - raise SystemExit(pytest.console_main()) + raise SystemExit(_console_main()) diff --git a/testing/test_config.py b/testing/test_config.py index 8d8328ac4cd..7886610242d 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -19,6 +19,7 @@ from _pytest.config import _strtobool from _pytest.config import Config from _pytest.config import ConftestImportFailure +from _pytest.config import console_main from _pytest.config import ExitCode from _pytest.config import parse_warning_filter from _pytest.config.argparsing import get_ini_default_for_type @@ -3132,3 +3133,12 @@ def test_prog_in_usage_cli(self, pytester: Pytester) -> None: """Usage line should show 'python -m pytest' when called from CLI subprocess.""" result = pytester.runpytest_subprocess("--help") result.stdout.fnmatch_lines(["usage: python -m pytest *"]) + + def test_console_main_deprecated(self, monkeypatch: pytest.MonkeyPatch) -> None: + """Calling pytest.console_main() should emit a deprecation warning.""" + monkeypatch.setattr("_pytest.config._console_main", lambda: 0) + with pytest.warns( + pytest.PytestRemovedIn10Warning, + match="pytest.console_main.*is deprecated", + ): + console_main() From 1c37f2999effaa194c5295c5836eef18fbdc4491 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 25 May 2026 20:27:28 +0200 Subject: [PATCH 4/4] docs: add pytest.console_main to nitpick_ignore The deprecated pytest.console_main() is referenced with :func: in the changelog fragment and deprecations.rst, but has no autofunction directive in the reference docs (intentionally, since it's being removed). Add it to nitpick_ignore so the RTD build passes. Co-authored-by: Cursor AI Co-authored-by: Anthropic Claude Opus 4 --- doc/en/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/en/conf.py b/doc/en/conf.py index 84b1c99e181..04ba1cfc616 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -117,6 +117,8 @@ ("py:class", "ScopeName"), ("py:class", "BaseExcT_1"), ("py:class", "ExcT_1"), + # Deprecated, intentionally not added to reference docs. + ("py:func", "pytest.console_main"), ] add_module_names = False