sphinx-autodoc-typehints icon indicating copy to clipboard operation
sphinx-autodoc-typehints copied to clipboard

More compact and more correct property description.

Open dvarrazzo opened this issue 4 years ago • 2 comments

Properties introspected by typehints are decorated with a "return type" clause. This can take up a lot of space and talking about "return type" for a property/attribute is not even that correct.

In psycopg3 documentation I have monkeypatched AttributeDocumenter.add_content() so that if an attribute has a :rtype: it is converted into a :type:, which is placed right after the directive. The difference can be seen in:

image

The code involved is something like what shown below, for reference. Of course the implementation in the module itself would be different.

    orig_attr_add_content = AttributeDocumenter.add_content

    def fixed_attr_add_content(self, more_content, no_docstring=False):
        """
        Replace a docstring such as::

            .. py:attribute:: ConnectionInfo.dbname
               :module: psycopg3

               The database name of the connection.

               :rtype: :py:class:`str`

        into:

            .. py:attribute:: ConnectionInfo.dbname
               :type: str
               :module: psycopg3

               The database name of the connection.

        which creates a more compact representation of a property.

        """
        orig_attr_add_content(self, more_content, no_docstring)
        if not isinstance(self.object, property):
            return
        iret, mret = match_in_lines(r"\s*:rtype: (.*)", self.directive.result)
        iatt, matt = match_in_lines(r"\.\.", self.directive.result)
        if not (mret and matt):
            return
        self.directive.result.pop(iret)
        self.directive.result.insert(
            iatt + 1,
            f"{self.indent}:type: {unrest(mret.group(1))}",
            source=self.get_sourcename(),
        )

    AttributeDocumenter.add_content = fixed_attr_add_content

def match_in_lines(pattern, lines):
    """Match a regular expression against a list of strings.

    Return the index of the first matched line and the match object.
    None, None if nothing matched.
    """
    for i, line in enumerate(lines):
        m = re.match(pattern, line)
        if m:
            return i, m
    else:
        return None, None

def unrest(s):
    r"""remove the reST markup from a string

    e.g. :py:data:`~typing.Optional`\[:py:class:`int`] -> Optional[int]

    required because :type: does the types lookup itself apparently.
    """
    s = re.sub(r":[^`]*:`~?([^`]*)`", r"\1", s)  # drop role
    s = re.sub(r"\\(.)", r"\1", s)  # drop escape

    # note that ~psycopg3.pq.ConnStatus is converted to pq.ConnStatus
    # which should be interpreted well if currentmodule is set ok.
    s = re.sub(r"(?:typing|psycopg3)\.", "", s)  # drop unneeded modules
    s = re.sub(r"~", "", s)  # drop the tilde

    return s

dvarrazzo avatar Apr 22 '21 19:04 dvarrazzo

A PR for this would be welcome.

gaborbernat avatar Jan 08 '22 11:01 gaborbernat

Setting :type: for properties based on the return type of the property function is addressed in Sphinx 4.0 via sphinx-doc/sphinx#7383. It seems that the :rtype: annotation for properties can be removed now. However, the types added by sphinx don't get run through typehints_formatter(), so it might be a good idea to include that functionality (if possible).

bryanforbes avatar Feb 02 '22 20:02 bryanforbes

I think this is resolved, partially by #287 and partially by the change to sphinx 4.0 mentioned above (probably some other changes were also involved, not sure).

hoodmane avatar Jan 20 '23 19:01 hoodmane