Skip to content

mypy plugin for @mark.parametrize #9334

Open

Description

@Jasha10

The idea is that a plugin would allow the arguments to the @pytest.mark.parametrize decorator to be type-checked.

For example:

import pytest

@pytest.mark.parametrize(
    "arg",
    [
        123,        # correct type int
        456.789,    # wrong type float, mypy plugin to complain
    ]
)
def test_foo(
    arg: int   # plugin reads `int` annotation here
) -> None:
    ...

Activity

  1. changed the title [-]mypy plugin for @mark.parametrizev[/-] [+]mypy plugin for @mark.parametrize[/+] on Nov 23, 2021
  2. changed the title [-]mypy plugin for @mark.parametrize[/-] [+]mypy plugin for `@mark.parametrize`[/+] on Nov 23, 2021
  3. The-Compiler commented on Nov 24, 2021

    @The-Compiler
    Member

    That seems like a good idea - but I don't think it should be part of pytest core.

  4. added
    type: proposalproposal for a new feature, often to gather opinions or design the API around the new feature
    on Nov 24, 2021
  5. tirkarthi commented on Jan 15, 2022

    @tirkarthi
    Contributor

    Nice idea. I found that there is typeguard project which can be used to perform run-time type checks.

    # conftest.py
    import typeguard
    
    
    def pytest_collection_modifyitems(session, config, items):
        for item in items:
            item.obj = typeguard.typechecked(item.obj)
    pytest -vvv          
    ==================================== test session starts ====================================
    platform linux -- Python 3.10.1, pytest-6.2.4, py-1.10.0, pluggy-0.13.1 -- /home/karthikeyan/stuff/python/myenv/bin/python
    cachedir: .pytest_cache
    rootdir: /tmp/testdir1
    plugins: forked-1.4.0, typeguard-2.13.3, anyio-3.5.0, mypy-plugins-1.9.3, xdist-2.5.0
    collected 2 items                                                                           
    
    tests/test_foo.py::test_foo[123] PASSED                                               [ 50%]
    tests/test_foo.py::test_foo[456.789] FAILED                                           [100%]
    
    ========================================= FAILURES ==========================================
    _____________________________________ test_foo[456.789] _____________________________________
    
    args = (), kwargs = {'arg': 456.789}, memo = <typeguard._CallMemo object at 0x7fd9fc7d2620>
    
        def wrapper(*args, **kwargs):
            memo = _CallMemo(python_func, _localns, args=args, kwargs=kwargs)
    >       check_argument_types(memo)
    
    /home/karthikeyan/stuff/python/myenv/lib/python3.10/site-packages/typeguard/__init__.py:1032: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    
    memo = <typeguard._CallMemo object at 0x7fd9fc7d2620>
    
        def check_argument_types(memo: Optional[_CallMemo] = None) -> bool:
            """
            Check that the argument values match the annotated types.
        
            Unless both ``args`` and ``kwargs`` are provided, the information will be retrieved from
            the previous stack frame (ie. from the function that called this).
        
            :return: ``True``
            :raises TypeError: if there is an argument type mismatch
        
            """
            if memo is None:
                # faster than inspect.currentframe(), but not officially
                # supported in all python implementations
                frame = sys._getframe(1)
        
                try:
                    func = find_function(frame)
                except LookupError:
                    return True  # This can happen with the Pydev/PyCharm debugger extension installed
        
                memo = _CallMemo(func, frame.f_locals)
        
            for argname, expected_type in memo.type_hints.items():
                if argname != 'return' and argname in memo.arguments:
                    value = memo.arguments[argname]
                    description = 'argument "{}"'.format(argname)
                    try:
                        check_type(description, value, expected_type, memo)
                    except TypeError as exc:  # suppress unnecessarily long tracebacks
    >                   raise TypeError(*exc.args) from None
    E                   TypeError: type of argument "arg" must be int; got float instead
    
    /home/karthikeyan/stuff/python/myenv/lib/python3.10/site-packages/typeguard/__init__.py:875: TypeError
    ================================== short test summary info ==================================
    FAILED tests/test_foo.py::test_foo[456.789] - TypeError: type of argument "arg" must be in...
    ================================ 1 failed, 1 passed in 0.14s ================================
    
  6. adamtheturtle commented on Aug 21, 2024

    @adamtheturtle

    I think that the mypy plugin couldn't work for indirect parametrisation, e.g. from the documentation:

    import pytest
    
    
    @pytest.fixture
    def fixt(request):
        return request.param * 3
    
    
    @pytest.mark.parametrize("fixt", ["a", "b"], indirect=True)
    def test_indirect(fixt):
        assert len(fixt) == 3

    This is where typeguard can help.

  7. phalberg commented on Oct 4, 2026

    @phalberg
    Contributor

    This one seems to not be so activate anymore, but would something like this be good practice when working with type checkers for this?
    For the following above i do get incompatible types error from mypy:

    import pytest
    
    cases: list[tuple[str, str]] = [
        ("text", False),
        ("other_text", 123),
    ]
    
    
    @pytest.mark.parametrize("input_value, expected_text", cases)
    def test_example_types(
        input_value: str, expected_text: str
    ) -> None:
        print(input_value)
        print(expected_text)

    But no errors when i run something like the following below:

    import pytest
    
    @pytest.mark.parametrize(
        "input, expected_text",
        [
            ("text", False),
            ("other_text", 123),
        ],
     )
    def test_example_types(
        input: str, expected_text: str,
    ) -> None:
        print(input)
       print(expected_text)

    Either way i have not tested the plugin but that probably works too -- also maybe this should be explained in the typing docs perhaps?
    Since this can technically have "silent" errors like i showed above for certain codebases if not careful enough...

    Any thoughts?

  8. adamtheturtle commented on Oct 5, 2026

    @adamtheturtle

    pytest-beartype is a good runtime option for this problem.

  9. adamtheturtle commented on Oct 5, 2026

    @adamtheturtle

    I have published mypy-pytest-parametrize with support for this (not indirect parametrisation).

    I'm happy to transfer ownership if pytest-dev folks want to own it.

  10. RonnyPfannschmidt commented on Oct 5, 2026

    @RonnyPfannschmidt
    Member

    hard reject - at first glance this looks like a quickly agent strung name squat

    we are not going to take ownership of something that looks prompt-dropped - whats wrong with people - we barely have time to react to all of the agent based bombard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: proposalproposal for a new feature, often to gather opinions or design the API around the new feature

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions