Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot collect test classes that are inherited from Protocol #12807

Open
ThirVondukr opened this issue Sep 12, 2024 · 7 comments
Open

Cannot collect test classes that are inherited from Protocol #12807

ThirVondukr opened this issue Sep 12, 2024 · 7 comments
Labels
topic: collection related to the collection phase type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature

Comments

@ThirVondukr
Copy link

System Info

  • Pytest Version: 8.3.3
  • OS: Windows 10

The problem

Currently pytest doesn't collect classes that contain __init__ which seems to conflict with typing.Protocol:

class _BaseTest(Protocol):
    sample_01: bytes
    cls: type[Union[HtpasswdFile, HtdigestFile]]


@typing.final
class TestHtpasswdFile(_BaseTest):
    sample_01 = SAMPLE_01
    cls = HtpasswdFile

typing.Protocol could be very useful if you want to have fields without having to create a method for them with @property.
Protocol seems to be patching __init__ for it's own one that raises a TypeError if it's a protocol, so perhaps an exception could be made for protocol classes here?

@dongfangtianyu
Copy link
Contributor

Hi, @ThirVondukr

It doesn't make sense to treat only protocol classes as exceptions.

Maybe you can try this approach:

class _BaseTest(Protocol):
    sample_01: bytes
    cls: type[Union[HtpasswdFile, HtdigestFile]]


delattr(_BaseTest, '__init__') # Remove unnecessary __init__ from parent class

@ThirVondukr
Copy link
Author

Simply deleting init from a class seems like a hack 🤔, it's not a huge issue but I think it's something that pytest should probably support

@RonnyPfannschmidt
Copy link
Member

It's tricky to pick reasonable exceptions

What's the intent for using a protocol there

@ThirVondukr
Copy link
Author

@RonnyPfannschmidt It's a way for defining an interface for child test classes (I want to parametrize a bunch of tests that way) and type check them that way, it's just more brief compared to using abc.ABC:

class ABCTest(ABC):
    @property
    @abstractmethod
    def a() -> A:
        raise NotImplementedError

    @property
    @abstractmethod
    def b() -> B:
        raise NotImplementedError
class ProtocolTest(Protocol):
    a: A
    b: B

@ThirVondukr
Copy link
Author

Honestly I could look into parametrized fixtures instead but classes seemed more explicit to me atm 🤔

@RonnyPfannschmidt
Copy link
Member

I believe it's a good idea for a new feature, lets start by just supporting protocol as that didn't exist when init was blocked

@ThirVondukr
Copy link
Author

Implementation wise I think you could either import typing._no_init_or_replace_init and check if SomeClass.__init__ is that, or just subclass protocol and get it's init:

from typing import Protocol


class A(Protocol):
    pass


class B(Protocol):
    pass


assert A.__init__ is B.__init__

@Zac-HD Zac-HD added type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature topic: collection related to the collection phase labels Oct 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic: collection related to the collection phase type: proposal proposal for a new feature, often to gather opinions or design the API around the new feature
Projects
None yet
Development

No branches or pull requests

4 participants