-
Notifications
You must be signed in to change notification settings - Fork 123
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
Add type annotations #225
Add type annotations #225
Conversation
`defaults` is the default values e.g. `1` in `foo=1`. It's just used to find the offset of the kwargs, but the code reused the name which made me scratch my head for a minute.
The test passes invalid values to the instance (it does not takes functions, it takes HookImpls). The test fails, but it accidentally used `return` instead of `assert` so it wasn't visible. Since what is being tested is evidently unimportant and legacy, just remove it.
It doesn't take `**kwargs` but an argument `kwargs`.
Codecov Report
@@ Coverage Diff @@
## master #225 +/- ##
==========================================
- Coverage 93.88% 89.04% -4.85%
==========================================
Files 9 14 +5
Lines 1129 1826 +697
Branches 21 124 +103
==========================================
+ Hits 1060 1626 +566
- Misses 66 185 +119
- Partials 3 15 +12
Continue to review full report at Codecov.
|
@bluetech definitely something I'm interested in introducing though I don't have time to look through it right now. I will get to it; feel free to incrementally ping if I take too long. |
from .manager import PluginManager | ||
|
||
_Writer = Callable[[str], None] | ||
_Processor = Callable[[Tuple[str, ...], Sequence[object]], None] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be Sequence[Any]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(and throughout)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you probably know, object
and Any
both accept any type, but they are opposites in terms of what you can do with them: with object
, you can't do much of anything (just __str__
and stuff) without narrowing it first with isinstance
, while Any
permits anything.
The rule I usually follow is: for inputs that can be anything, I use object
- this puts more restrictions on me and provides more assurances to the caller. For outputs, I use Any
, since I don't want to force the caller to perform isinstance
checks.
(Note: when the use provides a callback to be called by the library, the roles reverse, that is the arguments become Any
and the return type becomes object
).
I probably got it wrong in a few places so I'll go over it.
self.root = root | ||
self.tags = tags | ||
|
||
def __call__(self, *args): | ||
# type: (object) -> None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be # type: (*Any) -> None
def __getitem__(self, parameter): # type: ignore | ||
return object | ||
|
||
Generic = _GenericMeta() # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm I wonder if we shouldn't just add a dep on typing
for py2 and avoid some/most of these (albeit interesting!) fakes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think these are minor enough that a dependency is not worth it. That said, for me personally py2 is ancient history and the dep would certainly make things cleaner. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my vote is the typing;python_version="2.7"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'll switch to that.
One reason I forgot to mention is that putting everything under if False
avoids bringing in the heavy typing
import at runtime. But I suppose pytest already "pays" that so it's all the same.
"""Get the result(s) for this hook call. | ||
|
||
If the hook was marked as a ``firstresult`` only a single value | ||
will be returned otherwise a list of results. | ||
""" | ||
__tracebackhide__ = True | ||
if self._excinfo is None: | ||
return self._result | ||
return self._result # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the # type: ignore
because we know it can't be None
at this point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We know it has type _T
. However _T
itself can be NoneType
so an assert is not None
is not possible. Will add a comment.
BTW here is some interesting discussion about a nicer Result type with mypy: python/mypy#6936
|
||
try: # func MUST be a function or method here or we won't parse any args | ||
spec = _getargspec(func) | ||
if hasattr(inspect, "getfullargspec"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you have if sys.version_info > (...):
instead then the type annotation won't be needed here since mypy will know from typeshed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll change that.
if inspect.ismethod(func) or ( | ||
"." in getattr(func, "__qualname__", ()) and args[0] in implicit_names | ||
): | ||
qualname = getattr(func, "__qualname__", "") # type: str |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm why was the annotation necessary here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise it'd have type Any
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is that an error? sounds like the settings are cranked to an annoying level then :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not an error, just mypy doesn't know the type so we need to tell it. Can be done without the type comment if we use an hasattr
or a try-except
but the little comment seemed better.
|
||
def __getattr__(self, attr, default=None): | ||
def __getattr__(self, attr, default=None): # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the # type: ignore
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
warn_return_any
: Returning Any
from a function declared to return "str".
disallow_any_unimported = True | ||
disallow_subclassing_any = True | ||
disallow_untyped_calls = True | ||
disallow_untyped_defs = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find most of the any
settings more annoying than they are helpful -- they also tend to lead to lower quality annotations in my experience 🤷♂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even after everything is annotated?
This is blocked on some other work, I'll close this now - can be resubmitted later. |
Is this still blocked? I imagine a lot of the comment annotations could be changed to the normal syntax as indicated in #267 |
I rebased this branch now: https://github.com/bluetech/pluggy/commits/type-annotations I think it's good but still needs some self-review. |
Based on #224.
This PR adds type annotations to the library, using the Python 2-compatible syntax. The types are not exposed yet (a
py.typed
file is not distributed).Motivation: while working on adding type annotations to pytest, I noticed that pluggy is a core part of its machinery and that having type annotations for it will be useful. However, the annotations here turn out to not be that useful, because the main interface,
plugin_manager.hook.*
, remains untyped (see #191). The API is very dynamic and doesn't lend itself easily to static annotations, though it might be possible, maybe with a mypy plugin. But the types are still useful for all of the regular non-dynamic API. They also help when trying to understand the code base - the type of each variable and how all of the types fit together. Finally, they help catch bugs when the code changes and add confidence when refactoring.I tested it against pytest (feature branch): first that the test suite passes, and second that the type-checking passes (I manually added a
py.typed
file to pluggy in the venv to test that).This PR is probably not that easy to review, adds some maintenance burden, and I also didn't discuss it first. If you don't feel type annotations are desirable for pluggy,, I'll understand :)