Open
Description
What's the problem this feature will solve?
currently when creating a custom marker, there's no way for the type checker to ensure the arguments are correct. the builtin markers work around this by having type-checking only subtypes of MarkDecorator
like so (in _pytest/mark./structures.py
):
# Typing for builtin pytest marks. This is cheating; it gives builtin marks
# special privilege, and breaks modularity. But practicality beats purity...
if TYPE_CHECKING:
from _pytest.scope import _ScopeName
class _SkipMarkDecorator(MarkDecorator):
@overload # type: ignore[override,misc,no-overload-impl]
def __call__(self, arg: Markable) -> Markable:
...
@overload
def __call__(self, reason: str = ...) -> "MarkDecorator":
...
Describe the solution you'd like
a way to define markers in a type-safe way, similar to how the builtin markers are defined. maybe something like this:
# conftest.py
class Foo(MarkDecorator):
@override
def __call__(self, value: int) -> MarkDecorator: ...
# test_foo.py
from conftest import Foo
@Foo("asdf") # error: expected int, got str
def test_asdf(): ...
Alternative Solutions
remove the @final
from MarkGenerator
and allow plugins to subtype it with their custom markers:
# conftest.py
from pytest import MarkGenerator
class CustomMarkers(MarkGenerator):
foo: Callable[[int], MarkDecorator]
# test_foo.py
from conftest import CustomMarkers
mark = CustomMarkers()
@mark.foo("asdf") # error: expected int, got str
def test_asdf(): ...
this solution probably isn't the best though since there'd be no way to enforce that users use your subtype. whereas making each marker separate classes would at least allow you to enforce that @Foo
is used over @mark.foo
using --strict-markers