Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specify mypy types for pytest fixtures

I am trying to specify mypy type hints for the pytest native fixtures I am using in my test project e.g.:

import pytest

def pytest_configure(config):
    # Do something useful here

The config fixture returns a _pytest.config.Config object. If I try to model this naively:

import pytest

def pytest_configure(config: Config) -> None:
    # Do something useful here

I receive a mypy error: conftest.py:3: error: Name 'Config' is not defined [name-defined]

I could do from _pytest.config import Config, but this doesn't seem to be a good way, because _pytest is private. Another option would be to ignore the type with # type: ignore. If this is the recommended way I would of course do this, but I wonder if there is a better option.

I have the same issues in with any kind of pytest native fixtures I use, e.g. request which is used for parameterized fixtures. This would be a _pytest.fixtures.FixtureRequest.

like image 616
Rene Oschmann Avatar asked Dec 29 '20 11:12

Rene Oschmann


1 Answers

Importing from _pytest.config

Since pytest doesn't currently export Config (as of 6.2), the only way for typing is to use from _pytest.config import Config. This is how I also type config, as can be seen e.g. in this question of mine:

from _pytest.config import Config

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

You can track the typing progress in this pytest issue: #7469.

Custom type stubs

You can also introduce a small custom type stub that hides the reexport. It's questionable whether it will be useful here, only worth to mention for an alternative solution. If you create a file _typeshed/pytest.pyi with the following contents:

from typing import Any
from _pytest.config import Config as Config

def __getattr__(name: str) -> Any: ...  # incomplete

and make it accessible to mypy in mypy.ini:

[mypy]
mypy_path = _typeshed

Now you can import from pytest import Config at least in type checking mode - the runtime import will still fail. So the imports would look like

from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    from pytest import Config
else:
    Config = Any


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

The only benefit of that solution is that the private import is now hidden; I'd still go with the private import though.

like image 121
hoefling Avatar answered Sep 26 '22 10:09

hoefling