Skip to content

pluggy requires from __future__ import annotations although it shouldn't in Python 3.14 #629

@bersbersbers

Description

@bersbersbers

This file is valid in Python 3.14, meaning, python conftest.py does not error:

conftest.py:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from _pytest.config import Config

def pytest_configure(config: Config) -> None: ...

In Python 3.13, it requires from __future__ import annotations.

Now, running pytest with the above conftest.py raises the following error. For some reason, pluggy seems to want to read the annotations and fails because Config is not available in non-type-checking mode.

Potential workarounds include:

  • Config -> "Config" (but ruff undoes that)
  • remove if TYPE_CHECKING (but ruff undoes that)
  • add from __future__ import annotations

All of those work, but none of those should be necessary.

(.3.14) C:\Git\Temp>pytest
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "C:\Git\Temp\.3.14\Scripts\pytest.exe\__main__.py", line 10, in <module>
    sys.exit(console_main())
             ~~~~~~~~~~~~^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 221, in console_main
    code = main()
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 191, in main
    config = _prepareconfig(new_args, plugins)
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 358, in _prepareconfig
    config: Config = pluginmanager.hook.pytest_cmdline_parse(
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        pluginmanager=pluginmanager, args=args
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_hooks.py", line 512, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_callers.py", line 167, in _multicall
    raise exception
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_callers.py", line 139, in _multicall
    teardown.throw(exception)
    ~~~~~~~~~~~~~~^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\helpconfig.py", line 124, in pytest_cmdline_parse
    config = yield
             ^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_callers.py", line 121, in _multicall
    res = hook_impl.function(*args)
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 1155, in pytest_cmdline_parse
    self.parse(args)
    ~~~~~~~~~~^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 1525, in parse
    self.hook.pytest_load_initial_conftests(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        early_config=self, args=args, parser=self._parser
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_hooks.py", line 512, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_manager.py", line 120, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
           ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_callers.py", line 167, in _multicall
    raise exception
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_callers.py", line 139, in _multicall
    teardown.throw(exception)
    ~~~~~~~~~~~~~~^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\warnings.py", line 128, in pytest_load_initial_conftests
    return (yield)
            ^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_callers.py", line 139, in _multicall
    teardown.throw(exception)
    ~~~~~~~~~~~~~~^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\capture.py", line 173, in pytest_load_initial_conftests
    yield
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_callers.py", line 121, in _multicall
    res = hook_impl.function(*args)
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 1239, in pytest_load_initial_conftests
    self.pluginmanager._set_initial_conftests(
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        args=args,
        ^^^^^^^^^^
    ...<8 lines>...
        ),
        ^^
    )
    ^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 599, in _set_initial_conftests
    self._try_load_conftest(
    ~~~~~~~~~~~~~~~~~~~~~~~^
        anchor,
        ^^^^^^^
    ...<2 lines>...
        consider_namespace_packages=consider_namespace_packages,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 637, in _try_load_conftest
    self._loadconftestmodules(
    ~~~~~~~~~~~~~~~~~~~~~~~~~^
        anchor,
        ^^^^^^^
    ...<2 lines>...
        consider_namespace_packages=consider_namespace_packages,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 677, in _loadconftestmodules
    mod = self._importconftest(
        conftestpath,
    ...<2 lines>...
        consider_namespace_packages=consider_namespace_packages,
    )
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 753, in _importconftest
    self.consider_conftest(mod, registration_name=conftestpath_plugin_name)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 834, in consider_conftest
    self.register(conftestmodule, name=registration_name)
    ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\_pytest\config\__init__.py", line 519, in register
    plugin_name = super().register(plugin, name)
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_manager.py", line 161, in register
    hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_hooks.py", line 664, in __init__
    argnames, kwargnames = varnames(self.function)
                           ~~~~~~~~^^^^^^^^^^^^^^^
  File "C:\Git\Temp\.3.14\Lib\site-packages\pluggy\_hooks.py", line 313, in varnames
    sig = inspect.signature(
        func.__func__ if inspect.ismethod(func) else func  # type:ignore[arg-type]
    )
  File "C:\Users\bers\AppData\Roaming\uv\python\cpython-3.14.0-windows-x86_64-none\Lib\inspect.py", line 3312, in signature       
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
           ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   globals=globals, locals=locals, eval_str=eval_str,
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                   annotation_format=annotation_format)
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\bers\AppData\Roaming\uv\python\cpython-3.14.0-windows-x86_64-none\Lib\inspect.py", line 3027, in from_callable   
    return _signature_from_callable(obj, sigcls=cls,
                                    follow_wrapper_chains=follow_wrapped,
                                    globals=globals, locals=locals, eval_str=eval_str,
                                    annotation_format=annotation_format)
  File "C:\Users\bers\AppData\Roaming\uv\python\cpython-3.14.0-windows-x86_64-none\Lib\inspect.py", line 2502, in _signature_from_callable
    return _signature_from_function(sigcls, obj,
                                    skip_bound_arg=skip_bound_arg,
                                    globals=globals, locals=locals, eval_str=eval_str,
                                    annotation_format=annotation_format)
  File "C:\Users\bers\AppData\Roaming\uv\python\cpython-3.14.0-windows-x86_64-none\Lib\inspect.py", line 2325, in _signature_from_function
    annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str,
                                  format=annotation_format)
  File "C:\Users\bers\AppData\Roaming\uv\python\cpython-3.14.0-windows-x86_64-none\Lib\annotationlib.py", line 890, in get_annotations
    ann = _get_dunder_annotations(obj)
  File "C:\Users\bers\AppData\Roaming\uv\python\cpython-3.14.0-windows-x86_64-none\Lib\annotationlib.py", line 1059, in _get_dunder_annotations
    ann = getattr(obj, "__annotations__", None)
  File "C:\Git\Temp\conftest.py", line 7, in __annotate__
    def pytest_configure(config: Config) -> None: ...
                                 ^^^^^^
NameError: name 'Config' is not defined

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions