-
Notifications
You must be signed in to change notification settings - Fork 149
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 TypedDict versions of _Details type hint #128
Conversation
The names |
Thanks for this and especially the Pylance suggestion example. I am persuaded by your argument that it would be better to have explicit types for the different available keys. However, I think this implementation isn't quite right because one of the "extra" keys ( |
This seems relevant python/mypy#4617 |
I guess I should have looked at the actual source code first - apologies, I'll get that fixed rightaway. The combinatorics seem manageable for now (non-predicate, predicate, non-predicate-backoff, predicate-backoff -- if I'm not mistaking), though this will cascade if more custom fields are added to these dictionaries for other features, as you said. I don't think the class Details(TypedDict):
target: Callable[..., Any]
args: Tuple[Any, ...]
kwargs: Dict[str, Any]
tries: int
elapsed: float
class DetailsBackoff(Details):
wait: float Looking through the thread prompted me to test the current fallback types with Mypy though, which surfaced an issue: Mypy does not strictly follow conditionals, so dynamically redefining what the I have an idea for a workaround though, I'll look into that once the dictionary keys are fixed. |
Updated the types, they should now match what the implementation actually uses them for. The docstrings are a little overkill since any workflow benefitting from a |
Mypy's --allow-redefinition flag does not yet work with classes. There are workarounds that trick Mypy, but this leaves Pylance with a Union between Dict[str, Any] and the actual TypedDict subclass, which makes any function annotated with this type incompatible with the required handler signature. This solution always uses the TypedDict subclass, but reassigns the TypedDict name to equal the featureless "object" instead. That way inheritance works as intended and no typing_extensions dependency is required.
A summary of the aforementioned Mypy issue: Our Mypy does not allow this, and the The only reliable solution I was able to find was to skip the initial I will have a look for other solutions, but this seems like it's the only way to get both Mypy and Pylance supported without requiring external dependencies. |
Thanks for your work on this. I appreciate it especially how quick and responsive you've been. I'm trying to keep up as best I can! In the end I think I'm not comfortable with the type "explosion" here. Like you said it's wouldn't be that unmanageable with just 4 types, but I'm afraid it's going to end up being more confusing to the user to have these types which define just one more or less key. And I think it will be hard to name them in a way that reduces this confusion. And yes, as you said, there could end up being a future feature that make these types branch again. I'm thinking what we could do instead is something like: class _Details(TypedDict):
target: Callable[..., Any]
args: Tuple[Any, ...]
kwargs: Dict[str, Any]
tries: int
elapsed: float
class Details(_Details, total=False):
wait: float # this key will be present in the on_backoff handler case for either decorator
value: Any # this key will be present in the on_predicate decorator case In addition to the mypy issue I linked above, I think PEP 655 is also relevant. That's in a draft state, but it identifies the above pattern as the common (if cumbersome) solution. I think it may be wise to stick to this pattern and assume there might be a better way to do it eventually. I think we might still want to put |
This reverts commit 36f0e1f.
The additional mixed-in types added too much complexity and maintenance effort while still requiring the user to be aware of use-case specific keys.
I agree with your assessment; the previous solution was neither elegant nor concise enough, and there seem to be better options on the horizon. For this version, Mypy will flag invalid keys but will silently ignore that some keys might not exist at runtime (such as "wait"). Pylance will also flag invalid keys and warns that optional keys might not be available at runtime. |
I tested and played around with this some more, and I think I have worked out a way we can go back to falling back to details_kwargs = {"total": False}
if sys.version_info >= (3, 8):
from typing import TypedDict
else:
# use typing_extensions if installed but don't require it
try:
from typing_extensions import TypedDict
except ImportError:
TypedDict = Dict[str, Any]
del details_kwargs["total"]
class _Details(TypedDict):
target: Callable[..., Any]
args: Tuple[Any, ...]
kwargs: Dict[str, Any]
tries: int
elapsed: float
class Details(_Details, **details_kwargs):
wait: float # this key will be present in the on_backoff handler case for either decorator
value: Any # this key will be present in the on_predicate decorator case I have tested this with mypy and pylance, as well as at runtime, in each of python 3.9, python 3.6 (with type_extensions), and python 3.6 (without type extensions). Suggested completions in pylance are better with type_extensions installed (as expected) but otherwise it seems to work in all cases. |
That looks great - I overlooked that types are still valid runtime objects, this is much cleaner than via |
Although IMO it's not worth the fuzz just for supporting 3.6 and 3.7, I agree @bgreen-litl's suggestion looks very solid. @bgreen-litl once this PR lands, do you have any plans to release a |
@hoefling I just drafted a v2.0.0-alpha release with type hints and a few other things |
@bgreen-litl awesome! Do you plan to push alpha wheels to PyPI? |
@bgreen-litl bump slash +1 for the idea of pushing an alpha release to pypi. |
This adds the
backoff.types
namespace containing theDetailsException
andDetailsPredicate
types which may be used to type custom handlers in user code.Example of Pylance suggesting valid keys for this type of backoff handler
Here is a full example for illustration of what using these looks like:
Note: Using a plain ValueError as the exception class causes issues with Pylance. This seems isolated to this type checker and the tuple works as intended. I don't see anything wrong with the type hints used internally.