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 support for overloaded functions #239
Conversation
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 is excellent! Thanks for the changes. I've made some suggestions. If you don't want to tackle the refactoring then feel free to leave that and we can do it after this is merged.
else: | ||
sig = "({})".format(obj.args) | ||
if obj.return_annotation is not None: | ||
sig += " \u2192 {}".format(obj.return_annotation) |
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 is the advantage of using the unicode values 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.
Both →
and \u2192
are OK. I just copied → from Sphinx's this line.
By the way, the original autosummary doesn’t print return type annotations (maybe for saving spaces?).
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 wonder why that is. But you're right to match what Sphinx is doing so let's stick with what's here!
autoapi/mappers/python/parser.py
Outdated
if name in overloads: | ||
grouped = overloads[name] | ||
if single_data["doc"]: | ||
grouped["doc"] += "\n\n" + single_data["doc"] |
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'm not aware of any real convention around how the docstring of an overloaded function should be displayed. The most common way I've seen is the way that pybind does it.
"""my_method(*args, **kwargs) -> Any
This is the docstring for the primary definition.
1. my_method(arg0: str) -> None
This is the docstring for the first overload.
2. my_method(arg0: bool) -> None
This is the docstring for the second overload.
I don't think we'd need that first signature because we'll have already defined it in the :py:method
or :py:function
directive, but do you think that it would make the docstring more clear if we formatted it in this way?
This comment applies to the additions in parse_module
as well.
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 totally agree that we shouldn't write docstrings in overload definitions in standard .py (not .pyi) code. There is no way to access overloads’ docstring at runtime.
The only reason my code concatenates docstrings is to allow us to write documentation in .pyi stubs that cannot have actual implementations.
FYI: Autodoc doesn't support .pyi yet and it completely drops docstrings of overload definitions.
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.
Let's stick with ignoring the docstrings of overloads and use only the docstring of the actual implementation. It seems like overloads aren't supposed to have docstrings, plus Sphinx doesn't render them that way. Authors then have more control over how the docstring is rendered because they'll be formatting it however they like in the single primary docstring.
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.
Autoapi's pyi support is very useful and important for C-extension libraries. (Autodoc is also trying to support it (sphinx-doc/sphinx#4824))
If we completely ignore docstrings of overloads, we will be unable to write docs on Python stubs because stubs are not allowed to have actual implementations. I'll show an example:
test_invalid.pyi
from typing import overload, Union
def hello(a: int) -> int:
"""This is okay"""
@overload
def double(a: int) -> int: ...
@overload
def double(a: str) -> int:
def double(a: Union[str, int]) -> int:
"""DOCSTRING"""
run mypy test.pyi
:
test_invalid.pyi:10: error: An implementation for an overloaded function is not allowed in a stub file
Found 1 error in 1 file (checked 1 source file)
So we have to write the docstring in an overload.
test_valid.pyi:
from typing import overload, Union
def hello(a: int) -> int:
"""This is okay"""
@overload
def double(a: int) -> int: ...
@overload
def double(a: str) -> int:
"""DOCSTRING"""
run mypy --strict test_valid.pyi
Success: no issues found in 1 source file
Do you have any ideas about this?
I think employing only the last docstring is better than concatenating the all.
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.
When using stubgen to generate a stub file for a C extension written with pybind, it sorts the actual implementation to be the last.
So I agree. Let's use only the last docstring when there are no implementations of the function/method.
@@ -1,6 +1,13 @@ | |||
{% if obj.display %} | |||
.. function:: {{ obj.short_name }}({{ obj.args }}){% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %} | |||
{% for (args, return_annotation) in obj.signatures %} | |||
{% if loop.index0 == 0 %} |
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.
Would it make more sense to store the overloads in an attribute instead of storing the real function signature with it as well? I feel like the overloads more of a special case of the function.
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.
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 the result that you have looks good. What I meant was that we should store overloads as an attribute called "overloads" on the objects, whereas at the moment you have an attribute called signatures that stores all signatures.
So the template would then look like
.. function:: {{ obj.short_name }}({{ obj.args }}){% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %}
{% for (args, return_annotation) in obj.overloads %}
{{ obj.short_name }}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %}
{% endfor %}
@AWhetter Thanks for the review! |
I made the following changes:
|
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 looks good to me. Thanks for the amazing work!
This PR adds basic support for the
typing.overload
decorator.It works by storing all the overloaded signatures into the first definition.
Input:
Output:
Please check the
tests/python/py3example
for details.(This PR also fixes the issue that the autoapi silently drops duplicated methods in the same class.)
Close #217