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

Replace typing overloads with a functools.wraps declaration. #225

Merged
merged 3 commits into from
Jul 26, 2024

Conversation

jaraco
Copy link
Owner

@jaraco jaraco commented Apr 10, 2024

No description provided.

@jaraco jaraco mentioned this pull request Apr 10, 2024
@jaraco
Copy link
Owner Author

jaraco commented Apr 10, 2024

@bswck in #224 (comment) noted that this technique does in fact appear to do the right thing when types are validated with pylance, but not so much with mypy. That's actually pretty encouraging and suggests that may be the best approach for a library like this - to signal the intent and let the typing systems catch up. Especially if mypy is tracking this issue and has plans to implement it, I'd say that's good enough....

@jaraco
Copy link
Owner Author

jaraco commented Apr 10, 2024

python/mypy#10574 appears to be related. python/mypy#12573 suggests that mypy may be philosophically opposed to dynamic behavior.

@bswck
Copy link
Contributor

bswck commented Apr 10, 2024

I ended up with the silliest

open = open

being a working solution in both mypy and pylance.
The cutest part about it is that it also reduces the current wrapper implementation to a single assignment.
The doc is however copied entirely from builtins.open then, BUT the typing advantage is priceless.

@jaraco
Copy link
Owner Author

jaraco commented Apr 10, 2024

The doc is however copied entirely from builtins.open then

I suspect (and confirmed) that's also the case for functools.wraps(open), as that's part of what wraps does. The __doc__ could probably be retained by passing more parameters to functools.wraps (namely, passing the defaults minus __doc__), but that's unlikely to be elegant.

@bswck
Copy link
Contributor

bswck commented Apr 10, 2024

The doc is however copied entirely from builtins.open then

I suspect (and confirmed) that's also the case for functools.wraps(open), as that's part of what wraps does. The __doc__ could probably be retained by passing more parameters to functools.wraps (namely, passing the defaults minus __doc__), but that's unlikely to be elegant.

Yeah, it can be retained by assigned=tuple(set(functools.WRAPPER_ASSIGNMENTS) - {"__doc__"}) as I think about it rn.

@bswck
Copy link
Contributor

bswck commented Apr 10, 2024

It would be more useful for functools.wraps() to receive a parameter with attributes to skip. I'll try to suggest that in the Python community.

@jaraco
Copy link
Owner Author

jaraco commented Apr 10, 2024

It would be more useful for functools.wraps() to receive a parameter with attributes to skip. I'll try to suggest that in the Python community.

I wonder, though, if in this case, assigned=() would be the right thing to do.

@jaraco
Copy link
Owner Author

jaraco commented Apr 10, 2024

I've not used pylance. I have VSCode and I see Pylance warnings, but I'm unsure how to elicit the output of reveal_type.

@bswck
Copy link
Contributor

bswck commented Apr 10, 2024

It would be more useful for functools.wraps() to receive a parameter with attributes to skip. I'll try to suggest that in the Python community.

I wonder, though, if in this case, assigned=() would be the right thing to do.

Definitely this or ('__annotations__',), but I think the latter might be pretty enigmatic for others.

@bswck
Copy link
Contributor

bswck commented Apr 10, 2024

It would be more useful for functools.wraps() to receive a parameter with attributes to skip. I'll try to suggest that in the Python community.

I wonder, though, if in this case, assigned=() would be the right thing to do.

Definitely this or ('__annotations__',), but I think the latter might be pretty enigmatic for others.

Actually, nevermind. open/io.open doesn't have __annotations__ as it's a built-in with no runtime-bound type annotations, so that's a terrible idea 🤦‍♂️

@jaraco jaraco marked this pull request as ready for review July 26, 2024 23:34
@jaraco jaraco merged commit 00b04b7 into main Jul 26, 2024
2 of 21 checks passed
@jaraco jaraco deleted the feature/functools-wraps branch July 26, 2024 23:40
@jaraco
Copy link
Owner Author

jaraco commented Jul 27, 2024

I should have been more cautious about merging this. It breaks on Python 3.11 (only, not 3.10 or 3.12) during test collection:

________________________________________________________ ERROR collecting path/__init__.py ________________________________________________________
/opt/homebrew/Cellar/python@3.11/3.11.9_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/doctest.py:942: in find
    self._find(tests, obj, name, module, source_lines, globs, {})
/opt/homebrew/Cellar/python@3.11/3.11.9_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/doctest.py:1016: in _find
    self._find(tests, val, valname, module, source_lines,
/opt/homebrew/Cellar/python@3.11/3.11.9_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/doctest.py:1048: in _find
    self._find(tests, val, valname, module, source_lines,
/opt/homebrew/Cellar/python@3.11/3.11.9_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/doctest.py:1004: in _find
    test = self._get_test(obj, name, module, globs, source_lines)
/opt/homebrew/Cellar/python@3.11/3.11.9_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/doctest.py:1072: in _get_test
    lineno = self._find_lineno(obj, source_lines)
/opt/homebrew/Cellar/python@3.11/3.11.9_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/doctest.py:1121: in _find_lineno
    obj = inspect.unwrap(obj).__code__
E   AttributeError: 'builtin_function_or_method' object has no attribute '__code__'

The issue was reported in python/cpython#117692.

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.

2 participants