-
-
Notifications
You must be signed in to change notification settings - Fork 242
make wrapt a PEP561 typed package. #225
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
Conversation
not sure if the name have to be "wrapt.pyi" or "py.typed" - that needs to be tested.
correction : not sure if the name have to be "wrapt.pyi" or "wrappers.pyi" - that needs to be tested. |
Is the |
oh, sorry, yes, the |
Kind poke! |
I think you're correct. Typically, there's 1-1 for .py and .pyi file (technically one for each module), like in At the same time, I think (but I can't find the reference) that stub package can be shipped in a form of a single file, and that would be |
For a very matured project like wrapt I think its better to work with *.pyi files, because signatures dont change that frequently.
I never worked with pylance , I use pycharm - I am almost sure with pycharm that is not the case, but i did not try.
I would guess that 99% of the users only use the wrapt decorator and nothing else. So, to type check that, the signature of the wrapt decorator would be enough, and You can add signatures gradually as needed. Besides the "offline" type checkers, there is also a wonderful runtime type checker : https://github.com/beartype/beartype maybe You can team up with @leycec - he is a "Runtime type-checking aficionado. " |
stubs can be marked as |
💪 🐻 @leycec has been summoned to the chat. The chat proceeds to descend into chaos. Actually, I humbly agree with everything @bitranox has wisely spouted above – except this:
Yes. I strongly agree with this. In 2023, I see vanishingly few Pure-Python
Actually, it's probably the opposite; well-annotated That said, your mileage may vary (YMMV). This has been a public service announcement from the @beartype Broadcasting Service. Please do let me know if there is anything @beartype can explicitly do for |
@leycec , always funny and precise comments, love it. |
T = TypeVar("T", bound=Any) | ||
|
||
def decorator(wrapper: F, enabled: Optional[bool] = None, adapter: Optional[A] = None) -> F: ... | ||
|
||
class ObjectProxy(Generic[T]): |
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 that pyi files should also follow pep-8, which means 2 blank lines between functions and classes declared at the module scope.
Maybe just run ruff
or black
to auto-format this file?
A = TypeVar('A', bound=Callable[..., Any]) | ||
T = TypeVar("T", bound=Any) | ||
|
||
def decorator(wrapper: F, enabled: Optional[bool] = None, adapter: Optional[A] = None) -> F: ... |
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.
Due to microsoft/pyright#5929 the re-export in __init__.py
needs to be updated too.
For example, from .decorators import decorator as decorator
The issue affects pyright
but not mypy
.
You can test this by e.g.
poetry add git+https://github.com/GrahamDumpleton/wrapt.git#a2dddf6e10536f4cd4bfc96ff1a706cd71b0694c
poetry add pyright
poetry add mypy
pyright some_test_file.py
mypy some_test_file.py
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.
An alternative to from ... import A as A
would be to provide explicit __all__
in wrapt/__init__.py
.
Latest version of wrapt requires Python 3.6+ and in #261 someone recently suggested requiring Python 3.8+. So maybe someone wants to have a go at a separate PR now which embeds annotations in the code. Would appreciate it starting with core functionality first so I can follow along, learn, and verify myself since am not knowledgeable about using type annotations. |
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.
This did not work for me, so I managed to type the wrapt.decorator
in a different way, I think it's Py 3.7+
from typing import Any, Callable, Optional, TypeVar, Tuple, Dict, Protocol
R = TypeVar('R', covariant=True) # Return type of the function
T = TypeVar('T') # Type of instance (for methods)
class FuncProtocol(Protocol[R]):
def __call__(self, *args: Any, **kwargs: Any) -> R: ...
def decorator(
wrapper: Callable[
[
FuncProtocol[R],
Optional[T],
Tuple[Any, ...],
Dict[str, Any]],
R],
enabled: Optional[bool] = None
) -> FuncProtocol[R]: ...
https://gist.github.com/dimaqq/e4b418e1b9ce6a3cd874ba9540fe42c6
@bitranox give the current develop branch a look. partial typing stubs have been added: https://github.com/GrahamDumpleton/wrapt/blob/develop/src/wrapt/__init__.pyi |
Note that I decided to only provide type hints for Python 3.10+. Python 3.9 will go obsolete September or October this year. Making that decision means could use full syntax for type hints. I haven't tackled Couple of things have found are they unless you tell I only realised the latter when doing Anyway, am making progress. Have not really added type hints to code before for a package used by others, so has been a bit of a learning experience. |
It isn't actually binding which is a problem but a more fundamental problem that
So forces extra work on to the user of I have used |
Possibly relevant discussion, just link here so don't forget. |
maybe beartype can help to identify issues : https://github.com/beartype/beartype |
I wonder if smth like this would work: class _deco: …
def _deco(): …
class Deco(Protocol):
def __call__(self, …) -> …: pass
wrapt: Deco = _deco |
@bitranox: Thanks so much for the ping. You're so noice. 🤗
Interestingly, mypy supports a kinda hacky non-standard alternative to from typing import Protocol
class Combiner(Protocol):
def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ...
def batch_proc(data: Iterable[bytes], cb_results: Combiner) -> bytes:
for item in data:
... Crucially, note that Kinda weird, but occasionally useful. No idea whether callback protocols apply to this exact use case, though. It might be best to just "move on" and accept that strictly typing this edge case is non-trivial or even infeasible. Sadly, PEP 677 was rejected. Here's hoping that CPython devs accept a full-blown
You're doing a great job, @GrahamDumpleton! Some type hints are better than no hints. What you have now is almost certainly better than what you had before (i.e., nothing). Personally, I'd just call this QA Mona Lisa a done painting at some point. tl;dr: Ship this useful madness. 😂 |
I think I have pushed up now to As mentioned there are some limitations and also some corner cases where will give wrong errors/warnings by type checkers, but can't see they are fixable. I still need to finish up tests, and am going to create some new docs about limitations and workarounds for adding type hints to your own decorators so when used it will correctly complain. The docs might stimulate suggestions on how to improve type hints to make it work properly without workarounds. I am going to close this issue, but since no where else, can keep discussion going here for now if find anything. |
Oh, I could be a goose and me as dumb user wasn't setting up properly type hints for my decorator when using a wrapt decorator. So what I thought might be a limitation may not be after all. Still exploring. |
So now know how to make type signatures propagate properly so long as decorator is designed to only work on normal functions. If want type signature to work you can't use it on an instance or class method. Applying decorators to functions which have default arguments is also a problem as knowledge of default arguments is lost. 😩 |
It's a wicked problem, to be sure. @beartype resolves this to everyone's satisfies through a combination of PEP 484 from collections.abc import Callable
from typing import Any, TypeVar, Union, overload
DecoratableT = TypeVar(
'DecoratableT',
# Your decorator decorates objects that are either...
bound=Union[
# Arbitrary class *OR*...
type,
# Arbitrary callable *OR*...
Callable[..., Any],
# Other exotic kinds of objects if you support them. For example,
# @beartype also supports oddball stuff like C-based descriptors:
classmethod,
property,
staticmethod,
],
)
# These overloads satisfy static type-checkers like mypy and pyright:
@overload # type: ignore[misc,no-overload-impl]
def your_decorator(obj: DecoratableT) -> DecoratableT: ...
@overload
def your_decorator(define_any_optional_parameters_accepted_by_your_decorator_here: Any = None) -> (
Callable[[DecoratableT], DecoratableT]): ...
# This is your actual decorator implementation, which also needs to be well-typed:
def beartype(
# Optional positional or keyword parameters.
obj: Optional[DecoratableT] = None,
# Optional keyword parameters.
define_any_optional_parameters_accepted_by_your_decorator_here: Any = None,
) -> Union[DecoratableT, Callable[[DecoratableT], DecoratableT]]:
# Implement your decorator here, yo! That's sufficed for @beartype users for literally years. No complaints from @beartype's userbase that sprawls over like a billion packages now. Admittedly, it's pretty ugly and nonsensical syntax. What you gonna do, though? 😮💨 |
I added docs, so you can see details of current limitations in: The type hints I ended up adding can be seen in: The beartype package ones look rather tame to me. 🤣 |
WOW. That's... insane. I mean, that's good insane. To be sure, it's not the bad kind of insane. But "wow." I am stunned. That's probably the most intense use of callable-oriented type hints I've ever seen. You have taken type hints to the limit and blown way past that limit into a new event horizon of unseen horror. If that's still not good enough, then surely nothing more can be done. I bow before your QA might. 🙇 |
Are you able to try |
Will be a 2.0.0rc2 now. Someone suggested something to me which wouldn't work as was suggested with the way things worked in the way I did the type hints in wrapt, but it caused me to go back and try again an approach that had failed at before. In doing that I found a quirk of how type hints was working that was causing a prior attempt to fail. Thus will be able to change things and solve the problems of optional and keyword params not working properly, but in doing so will mean the way type hint is written for wrapped argument of wrapper when declaring you own decorators has to change. |
not sure if the name have to be "wrapt.pyi" or "py.typed" - that needs to be tested.