crusaderky
crusaderky

Reputation: 2692

Sphinx :ivar tag goes looking for cross-references

I want to document Python object attributes with Sphinx. I understand I should use

:ivar varname: description
:ivar type varname: description

However I'm seeing a weird behaviour, that is Sphinx searches my project for the variable name and tries to create symlinks. E.g. this code:

class A(object):
    """
    :ivar x: some description
    """
    def __init__(self, x):
        self.x = x

class B(object):
    def x(self):
        return 1

class C(object):
    def x(self):
        return 2

will cause this error:

module1.py:docstring of mylibrary.module1.A:None: WARNING: more than one target found for cross-reference u'x': mylibrary.module1.C.x, mylibrary.module1.B.x

Did I understand incorrectly the purpose or usage of :ivar?

Upvotes: 17

Views: 3293

Answers (6)

Martin Pecka
Martin Pecka

Reputation: 3063

For those running older Sphinx versions than 4.0, here is a much simpler fix than the monkey-patch from @mzjn that was tested back to Sphinx 1.6.7 from Ubuntu 18.04. The fix basically just does the same thing that was done in PR #8638 that fixed the issue.

Put the following code into your conf.py:

from sphinx.domains.python import PyObject
PyObject.doc_field_types[map(lambda f: f.name == 'variable', PyObject.doc_field_types).index(True)].rolename = None

Upvotes: 0

Greg Dubicki
Greg Dubicki

Reputation: 6940

This is finally fixed in Sphinx 4.0.0.beta1 released on 2021-04-12.

The next stable release of Sphinx that contains it will be 4.0 or 4.1.

(Issue: #5977 , PR that fixed it: #8638, changelog entry for 4.0 beta 1: here)

Upvotes: 0

fritzo
fritzo

Reputation: 497

Here is a workaround provided by @acrisci on github: prefix your variable name with ~.. For example replace

:ivar float bandwidth: blah

with this:

:ivar float ~.bandwidth: blah

Source: https://github.com/sphinx-doc/sphinx/issues/2549#issuecomment-488896939

Upvotes: 3

mzjn
mzjn

Reputation: 50957

Here is a monkey patch (based on Sphinx 1.5.1) that disables ivar cross-references. I'm not sure what the best solution is, so consider the patch an experimental suggestion. To try it out, add the code below to conf.py.

from docutils import nodes
from sphinx.util.docfields import TypedField
from sphinx import addnodes

def patched_make_field(self, types, domain, items):
    # type: (List, unicode, Tuple) -> nodes.field
    def handle_item(fieldarg, content):
        par = nodes.paragraph()
        par += addnodes.literal_strong('', fieldarg)  # Patch: this line added
        #par.extend(self.make_xrefs(self.rolename, domain, fieldarg,
        #                           addnodes.literal_strong))
        if fieldarg in types:
            par += nodes.Text(' (')
            # NOTE: using .pop() here to prevent a single type node to be
            # inserted twice into the doctree, which leads to
            # inconsistencies later when references are resolved
            fieldtype = types.pop(fieldarg)
            if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text):
                typename = u''.join(n.astext() for n in fieldtype)
                par.extend(self.make_xrefs(self.typerolename, domain, typename,
                                           addnodes.literal_emphasis))
            else:
                par += fieldtype
            par += nodes.Text(')')
        par += nodes.Text(' -- ')
        par += content
        return par

    fieldname = nodes.field_name('', self.label)
    if len(items) == 1 and self.can_collapse:
        fieldarg, content = items[0]
        bodynode = handle_item(fieldarg, content)
    else:
        bodynode = self.list_type()
        for fieldarg, content in items:
            bodynode += nodes.list_item('', handle_item(fieldarg, content))
    fieldbody = nodes.field_body('', bodynode)
    return nodes.field('', fieldname, fieldbody)

TypedField.make_field = patched_make_field

The original TypedField.make_field method is here: https://github.com/sphinx-doc/sphinx/blob/master/sphinx/util/docfields.py.

Upvotes: 5

saaj
saaj

Reputation: 25244

There's an alternative with other advantages. Just define you member variables at class scope and document them with plain docstring. Later you can reference them with py:attr: role. It's more readable, self-documented (yeah, I know this is under , but anyway) and introspection-friendly approach.

module.py

class A:

    x = None
    '''This way there's no issue. It is more readable and friendly
    for class member introspection.'''


    def __init__(self, x):
        self.x = x

class B:
    '''Something about :py:attr:`.A.x`'''

    def x(self):
        '''Method x of B'''
        return 1

README.txt

****
Test
****

.. autoclass:: module.A
   :members:

.. autoclass:: module.B
   :members:

conf.py

extensions = ['sphinx.ext.autodoc']

source_suffix = '.txt'

master_doc = 'README'

project = 'Test'

pygments_style = 'sphinx'

html_theme = 'alabaster'

html_use_index       = False
html_show_sourcelink = False
html_show_copyright  = False

html_sidebars = {'**': ['localtoc.html']}

Build like PYTHONPATH=. sphinx-build . html.

Upvotes: 1

rll
rll

Reputation: 5587

As mzjn referred there is an open issue for this SO post. In that thread there is also already a work-around for the issue posted. In sum, you use inline comments #: instead of the docstring.

Take a look at the python.py file in the commit referred by the user here. The docstring entries were removed (red lines), and he added inline comments in the constructor (green lines).

I have been looking for documentation on this but could not find it. For instance:

(...)
def __init__(self, function, fixtureinfo, config, cls=None, module=None):
    #: access to the :class:`_pytest.config.Config` object for the test session
    self.config = config
    (...)

As noted by Nick Bastin this work-around renders completely differently from :ivar:. There is no type support, and it always renders the default value.

Upvotes: 1

Related Questions