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

ENH: Add support for type annotations #601

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

RastislavTuranyi
Copy link

Resolves #196

Hi! I ran into the issue that numpydoc does not support typehints before Christmas, so I decided to have a crack at it. I have tried multiple different approaches (not in this git history), especially trying to combine numpydoc with sphinx-autodoc-typehints, but what I finally settled on is to make use of numpydoc's powerful class system (I have found that the data representation of numpydoc and sphinx-autodoc-typehints are largely incompatible).

My implementation edits the _parse_param_list method - responsible for creating the docstrings for Parameters, Other Parameters, Attributes, Methods, Returns, Yields, Raises, and Receives sections - so that a type can be obtained from the type hint if one is not specified in the docstring (btw, I believe it should be relatively simple to implement #356 following a similar method). This means that the type hints can be supported for all those sections with minimal code. The way this works is defined in a new method, _get_type_from_signature, which by default on NumpyDocString only returns an empty string (no type). The actual type inference is implemented on the subclasses:

FunctionDoc

Getting type hints for functions is the easier task since we can rely fully on inspect.signature. Firstly, the signature is obtained in __init__ to avoid recomputing it at every iteration of _parse_param_list. All that is then necessary in _get_type_from_signature is to return the type hint associated with the argument name, provided that the function has a signature and that the type hint is set.

ClassDoc

At the basic level, the type hints for classes are similar to FunctionDoc and the code is therefore repeated (there is potentially room for optimisation where the code could be pulled out to NumpyDocString, should that be desirable). However, there is an additional complication in that the class documentation may contain the attributes (which may be @property) and therefore are not part of the __init__ signature. This required an additional method, _find_type_hints:

  • First is handled the case where the arg_name is an attribute with a type annotation (typing.get_type_hints handles unwrapping etc.)
    • The second try except block handles the case where the annotation is a normal class (by getting __name__) or an annotation from types.
        type_hints = get_type_hints(cls)
        try:
            annotation = type_hints[arg_name]
        except KeyError:
            ...

        try:
            return str(annotation.__name__)
        except AttributeError:
            return str(annotation)
  • In the ..., the remaining options are that the arg_name doesn't exist, handled via:
            try:
                attr = getattr(cls, arg_name)
            except AttributeError:
                return ""
  • Or that it is a callable, in which case the return annotation is found via inspect.signature:
            attr = attr.fget if isinstance(attr, property) else attr

            if callable(attr):
                try:
                    signature = inspect.signature(attr)
                except ValueError:
                    return ""

                if signature.return_annotation == signature.empty:
                    return ""
                else:
                    return str(signature.return_annotation)
  • Or that it is a class variable without an annotation (but with the value set), in which case it can be extracted:
            else:
                return type(attr).__name__

Robustness

From my testing, this implementation is able to handle everything my other projects could throw at it, but I would not be surprised if there are edge cases where it breaks down. I have not yet written any unit tests since I wasn't sure what the best way to do that in this repository is, or whether this is the method for solving #196 that you want to implement. Regardless, though, I think it might be possible to increase the robustness of my implementation by using some of the type-extraction functions from sphinx-autodoc-typehints (whether by depending on it as a whole, or by copying the relevant functions, I am unsure), such as get_annotation_args or format_annotation.

Conclusion

Please let me know what you think! I am keen to get numpydoc working with type annotations, and I believe that this implementation is a straightforward way to do that, but I am curious what your plans are for this issue.

RastislavTuranyi and others added 4 commits January 4, 2025 12:28
For some functions, the signature cannot be obtained and so inspect.signature raises a ValueError, which can crash the sphinx build. This has been fixed by catching the exception
For ClassDoc, the _get_type_from_signature method handles not only the docs for __init__, but also for the Attribute and Method etc. sections, whose typehints cannot be obtained from signature(__init__). Therefore, further code has been added that attempts to find the type hint from different sources and should work for functions, methods, properties, and attributes.
ForwardRef is no longer necessary since the functionality is handled via typing.get_type_hints
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.

Support type annotations (PEP 484)
1 participant