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

Fix pytest_ignore_collect hooks: do not return False #6778

Merged
merged 4 commits into from
Feb 22, 2020

Conversation

blueyed
Copy link
Contributor

@blueyed blueyed commented Feb 20, 2020

It should only return True when something is to be ignored, not
False otherwise typically.

This caused e.g. bad interaction with the cacheprovider (before
#6448).

It should only return `True` when something is to be ignored, not
`False` otherwise typically.

This caused e.g. bad interaction with the cacheprovider (before
pytest-dev#6448).
@nicoddemus
Copy link
Member

Can you elaborate a bit? I ask because I don't see anywhere in the code where we make a distinction between None and False when calling pytest_ignore_collect; they are all in the form:

if ihook.pytest_ignore_collect(path=path, config=self.config):
    ...

@blueyed
Copy link
Contributor Author

blueyed commented Feb 20, 2020

It's a firstresult hook (and None is not a result there then).

@blueyed
Copy link
Contributor Author

blueyed commented Feb 20, 2020

@nicoddemus please suggest how to improve the doc then even more - since it appears to be still unclear.

@nicoddemus
Copy link
Member

Ahh got it, the key point is the fact the hook is firstresult, sorry my bad. 👍

@blueyed
Copy link
Contributor Author

blueyed commented Feb 20, 2020

Ahh got it, the key point is the fact the hook is firstresult, sorry my bad.

Can this be improved in the doc then to make it clearer?

@@ -198,7 +198,8 @@ def pytest_ignore_collect(path, config):
This hook is consulted for all files and directories prior to calling
more specific hooks.

Stops at first non-None result, see :ref:`firstresult`
Stops at first non-None result, see :ref:`firstresult`, i.e. you should
Copy link
Member

@nicoddemus nicoddemus Feb 20, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be improved in the doc then to make it clearer?

Perhaps this?

    As any other :ref:`firstresult` hook, this stops at first non-`None` result. This hook
    can return `False` explicitly to prevent a file to be ignored by other hooks.

Copy link
Contributor Author

@blueyed blueyed Feb 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pulled out the doc change.
There are more places using the same sentence, and this should be fixed across all of them, maybe adding and linking to an intermediate doc section then.
=> #6787

def pytest_ignore_collect(path, config):
def pytest_ignore_collect(
path: py.path.local, config: Config
) -> "Optional[Literal[True]]":
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bluetech I assume using Literal[True] here makes sense now? (although not in the hookspec itself)
(I've quickly checked your other typing PRs, but it was not included there, is it?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General question: is the hook implementation allowed to declare a return type that is different, but still a subset, of the return type declared by the hookspec?

Example:

@hookspec
def pytest_ignore_collect(
    path: py.path.local, config: Config
) -> "Optional[bool]":
    ...

@hookimpl
def pytest_ignore_collect(
    path: py.path.local, config: Config
) -> "Optional[Literal[True]]":
    ...

Copy link
Contributor Author

@blueyed blueyed Feb 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicoddemus
Yes, this is allowed in general:

from typing import Literal
from typing import Optional


class A:
    def f(self) -> Optional[bool]:
        pass


class B(A):
    def f(self) -> Literal[True]:
        return True

reveal_type(A().f())  # Revealed type is 'Union[builtins.bool, None]'
reveal_type(B().f())  # Revealed type is 'Literal[True]'

(keep in mind though that mypy currently does not know that hookimpls are subsets of hookspecs (as with subclasses))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blueyed

@bluetech I assume using Literal[True] here makes sense now? (although not in the hookspec itself)

There are arguments for and against.

For: the type provides a quick indication that this impl either only returns None or True. Can gain useful semantic information just from the type, namely that it doesn't return False.

Against: If sometime the circumstances change, and the function wants to start returning False, that's perfectly legit logically, but the type needs to change. This suggests the type is over restrictive.

Against: Might confuse the person who wants to make the change, if they don't realize they are allowed to extend the type. Might confuse copy/pasters. (Note: the argument is weakened by the fact hookimpls are already allowed to omit arguments they don't use, so might not match the hookspec already).

My inclination is to use the more general type and match the hookspec, but I can see both ways, and am probably not too consistent myself. So I'm fine with whatever you decide.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bluetech
Thanks for elaborating on it again. I think the benefit of gaining useful semantic information just from the type is useful here in general.

Will wait for any other feedback, and otherwise merge it as is.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is allowed in general

Yes thanks, that's exactly what I meant to ask (not about hooks/impls, but in general).

Will wait for any other feedback, and otherwise merge it as is.

I'm fine with what you guys decide, and the current form. 👍

@blueyed blueyed merged commit 1d5a0ef into pytest-dev:master Feb 22, 2020
@blueyed blueyed deleted the fix-pytest_ignore_collect-upstream branch February 22, 2020 22:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants