-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
use ParamSpec for pytest.skip instead of __call__ Protocol attribute #13445
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
base: main
Are you sure you want to change the base?
Conversation
This is a more canonical way of typing generic callbacks/decorators (see https://mypy.readthedocs.io/en/stable/protocols.html#callback-protocols) This helps with potential issues/ambiguity with `__call__` semanics in type checkers -- according to python spec, `__call__` needs to be present on the class, rather than as an instance attribute to be considered callable. This worked in mypy because it didn't handle the spec 100% correctly (in this case this has no negative consequences) However, `ty` type checker is stricter/more correct about it and every `pytest.skip` usage results in: `error[call-non-callable]: Object of type `_WithException[Unknown, <class 'Skipped'>]` is not callable` For more context, see: - astral-sh/ruff#17832 (comment) - https://discuss.python.org/t/when-should-we-assume-callable-types-are-method-descriptors/92938 Testing: Tested with running mypy against the following snippet: ``` import pytest reveal_type(pytest.skip) reveal_type(pytest.skip.Exception) reveal_type(pytest.skip(reason="whatever")) ``` Before the change: ``` test_pytest_skip.py:2: note: Revealed type is "_pytest.outcomes._WithException[def (reason: builtins.str =, *, allow_module_level: builtins.bool =) -> Never, def (msg: Union[builtins.str, None] =, pytrace: builtins.bool =, allow_module_level: builtins.bool =, *, _use_item_location: builtins.bool =) -> _pytest.outcomes.Skipped]" test_pytest_skip.py:3: note: Revealed type is "def (msg: Union[builtins.str, None] =, pytrace: builtins.bool =, allow_module_level: builtins.bool =, *, _use_item_location: builtins.bool =) -> _pytest.outcomes.Skipped" test_pytest_skip.py:4: note: Revealed type is "Never" ``` After the change: ``` test_pytest_skip.py:2: note: Revealed type is "_pytest.outcomes._WithException[[reason: builtins.str =, *, allow_module_level: builtins.bool =], Never, def (msg: Union[builtins.str, None] =, pytrace: builtins.bool =, allow_module_level: builtins.bool =, *, _use_item_location: builtins.bool =) -> _pytest.outcomes.Skipped]" test_pytest_skip.py:3: note: Revealed type is "def (msg: Union[builtins.str, None] =, pytrace: builtins.bool =, allow_module_level: builtins.bool =, *, _use_item_location: builtins.bool =) -> _pytest.outcomes.Skipped" test_pytest_skip.py:4: note: Revealed type is "Never" ``` All types are matching and propagated correctly.
a0735e1
to
58892a3
Compare
Thanks! This looks good to me. But looking at this again, I wonder if it wouldn't be simpler (in terms of complexity, not in terms of lines of code) to turn these functions into callable classes? Then the |
Forgot to mention, the CI failures are unrelated and fixed in main -- rebase should take care of it. |
Hi @bluetech -- sorry, just got around to try this, did on a separate branch (just on Something like this (If I understood your suggestion correctly)
This generally works, and seems to result in correct runtime/typecheck time types. However it fails this test: pytest/testing/python/collect.py Lines 1075 to 1081 in 9e9633d
Which kinda makes sense -- python would normally print just Apart from that, the remaining asserts pass if I change the assert to So up to you if you'd prefer me to convert the rest ( |
This is a more canonical way of typing generic callbacks/decorators
(see https://mypy.readthedocs.io/en/stable/protocols.html#callback-protocols)
This helps with potential issues/ambiguity with
__call__
semanics in type checkers -- according to python spec,__call__
needs to be present on the class, rather than as an instance attribute to be considered callable.This worked in mypy because it didn't handle the spec 100% correctly (in this case this has no negative consequences)
However,
ty
type checker is stricter/more correct about it and everypytest.skip
usage results in:error[call-non-callable]: Object of type _WithException[Unknown, <class 'Skipped'>] is not callable
For more context, see:
Protocol[]
as generic astral-sh/ruff#17832 (comment)Testing:
Tested with running mypy against the following snippet:
Before the change:
After the change:
All types are matching and propagated correctly.