Sphinx nit-picky mode but only for links I explicitly wrote - python-sphinx

I tried turning on Sphinx's nit-picky mode (-n) to catch any broken links I might have accidentally made. However, it spews out errors for all the places where I've documented types. In some cases I've described types semantically (e.g. "3D array"), but it does it even for types extracted from type hints (even with intersphinx set up to pull Python types). For example, for this module
from typing import Callable
def foo(x: Callable[..., int]):
pass
I get the error docstring of myproj.foo:: WARNING: py:class reference target not found: Callable[..., int]. That's with only sphinx.ext.autodoc and sphinx.ext.intersphinx extensions and a freshly-generated conf.py.
Is there some way to prevent Sphinx from trying to generate links for type information, or at least stop it complaining when they don't exist while still telling me about bad links in my hand-written documentation?
I'm using Sphinx 3.0.3.

Perhaps nitpick_ignore will do what you want? In your conf.py, something like this:
nitpick_ignore = [
("py:class", "Callable"),
]
I'm not sure of the exact values in the tuple that should be used, but I got the idea from this issue and a linked commit.

I had success solving a similar problem by writing a custom sphinx transform. I only wanted warnings for cross-references to my own package's python documentation. The following can be saved as a python file and added to extensions in conf.py once it is on the python path.
from sphinx import addnodes
from sphinx.errors import NoUri
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util import logging
logger = logging.getLogger(__name__)
class MyLinkWarner(SphinxPostTransform):
"""
Warns about broken cross-reference links, but only for my_package_name.
This is very similar to the sphinx option ``nitpicky=True`` (see
:py:class:`sphinx.transforms.post_transforms.ReferencesResolver`), but there
is no way to restrict that option to a specific package.
"""
# this transform needs to happen before ReferencesResolver
default_priority = 5
def run(self):
for node in self.document.traverse(addnodes.pending_xref):
target = node["reftarget"]
if target.startswith("my_package_name."):
found_ref = False
with suppress(NoUri, KeyError):
# let the domain try to resolve the reference
found_ref = self.env.domains[node["refdomain"]].resolve_xref(
self.env,
node.get("refdoc", self.env.docname),
self.app.builder,
node["reftype"],
target,
node,
nodes.TextElement("", ""),
)
# warn if resolve_xref did not return or raised
if not found_ref:
logger.warning(
f"API link {target} is broken.", location=node, type="ref"
)
def setup(app):
app.add_post_transform(MyLinkWarner)

Related

Bullet list style for single parameter functions in RTD theme using autodoc and Sphinx?

I've noticed that when I use autodoc with the ReadTheDoc theme, if I have multiple arguments in my functions they are listed in a bullet list style:
arg1
arg2
...
but if there is only 1 argument then it is not using the bullet list style which is a bit silly to me since it breaks the continuity of the design.
I've found how to remove the disc via CSS to make things more uniform but I actually want to do the opposite and have the disk for the single argument functions.
At this point, I'm not sure it is a CSS change and I do not know how to do that.
I've also noticed the same thing in different docs.
Here is the rendered html:
Here are the 2 methods:
def add_attribute(self, name, index):
"""
:param name: The name attached to the attribute.
:param index: The position of the attribute within the list of attributes. """
print("")
def delete_attribute(self, name):
"""
:param name: The name of the attribute to delete."""
print("")
Here is the my .rst:
API
----------------
.. automodule:: my_module
:members:
Here is the conf.py
extensions = [
'sphinx_rtd_theme',
'sphinx.ext.autodoc',
'sphinx.ext.napoleon',
'sphinx.ext.coverage',
'sphinx.ext.autosummary',
]
templates_path = ['_templates']
language = 'python'
exclude_patterns = []
html_theme = "sphinx_rtd_theme"
html_static_path = ['_static']
autosummary_generate = True
Any idea?
Cheers!
After a lot of digging, I've found a partial workaround for this.
My solution involves manually editing the produced HTML files to insert the missing bullet points.
Required conf.py changes:
# Register hook to run when build is complete
def setup(app):
app.connect('build-finished', on_build_finished)
# Hook implementation
def on_build_finished(app, exception):
add_single_param_bullets("_build/html/index.html")
# Function to actually add the bullet points by overwriting the given HTML file
def add_single_param_bullets(file_path):
print('Add single parameter bullets in {:s}'.format(file_path))
if not os.path.exists(file_path):
print(' File not found, skipping...')
return
lines_enc = []
with open(file_path, 'rb') as f:
for l in f.readlines():
# Check for html that indicates single parameter function
if b'<dd class="field-odd"><p><strong>' in l:
# Work out the encoding if not defined
enc = None
if enc is None:
import chardet
enc = chardet.detect(l)['encoding']
# Decode html and get the parameter information that needs adding
l_dec = l.decode(enc)
l_insert = l_dec.replace('<dd class="field-odd">', '').replace('\r\n', '')
# Add new encoded lines to output
lines_enc.append('<dd class="field-odd"><ul class="simple">'.encode('utf=8'))
lines_enc.append('<li>{:s}</li>'.format(l_insert).encode(enc))
lines_enc.append('</ul>'.encode('utf=8'))
else:
lines_enc.append(l)
# Overwrite the original file with the new changes
with open(file_path, 'wb') as f:
for l in lines_enc:
f.write(l)
In my case, I only have single argument functions in index.html. However, you can register additional files in on_build_finished.
A few things to note:
This only edits the produced HTML files, and doesn't actually solve the underlying problem. I dug through the source for a bit but couldn't find why the bullet points aren't added for single parameter function.
The problem is not just for the RTD theme. It seems to occur with the basic theme as well. So I suspect it's a deeper problem with Sphinx rather than the RTD theme.
The code above somewhat deals with different encodings in the original HTML.
This does not work on the RTD website. As the HTML files are edited in place, and the RTD build outputs the HTML files to a different directory, this solution doesn't seem to work on the RTD website. This is quite annoying. A solution would be to somehow change the RTD build process, or tell RTD to use pre-built HTML sources rather than building its own, but I don't know how to do so.
After spending a few hours working all this out, I actually think it looks better without the bullet points...

PyGtk3 and Gettext - problem choosing the language at run-time

Long story short, when I thought the strings would be translated, nothing happens.
I have a PyGtk application and a module where I store all the strings that I want to be translated in the following way:
'''
Strings module.
'''
....
CANCEL_BUTTON_TEXT = _("Cancel")
BACK_BUTTON_TEXT = _("Back")
....
And so on. So then they are used from other modules like:
'''
View module.
'''
import strings
# Usage example
button.set_label(strings.CANCEL_BUTTON_TEXT)
button.set_tooltip(strings.TOOLTIP)
window_title.set_title(strings.WINDOW_TITLE)
...
I have created the necessary .mo files. This is the jerarquy I have:
/locales
/es
LC_MESSAGES
base.mo
base.po
/en
LC_MESSAGES
base.mo
base.po
base.pot
As the documentation says (https://docs.python.org/3/library/gettext.html), in my main.py I have the following lines:
import gettext
gettext.install('myapplication')
es_lang = gettext.translation('base', localedir='locales', languages=['es'])
en_lang = gettext.translation('base', localedir='locales', languages=['en'])
es_lang.install()
Then, in my application I have a button, that when is pressed, the following line is executed:
en_lang.install()
But the spanish language is still used on the widgets. Could anyone help me out?
So, what I was doing wrong is I was translating the strings but I wasn't updating the GTK Widgets labels, among other things.
The way I solved this was:
i) Created a class where I put all the strings I want to translate when the user selects a language. That class is the only one to import the gettext module.
ii) When the user selects a language, a method of that class is called and it translates all strings to that language.
iii) Another method outside that class updates all labels and widgets that use those strings.

How to add link to source code in Sphinx

class torch.FloatStorage[source]
byte()
Casts this storage to byte type
char()
Casts this storage to char type
Im trying to get some documentation done, i have managed to to get the format like the one shown above, But im not sure how to give that link of source code which is at the end of that function!
The link takes the person to the file which contains the code,But im not sure how to do it,
This is achieved thanks to one of the builtin sphinx extension.
The one you are looking for in spinx.ext.viewcode. To enable it, add the string 'sphinx.ext.viewcode' to the list extensions in your conf.py file.
In summary, you should see something like that in conf.py
extensions = [
# other extensions that you might already use
# ...
'sphinx.ext.viewcode',
]
I'd recommend looking at the linkcode extension too. Allows you to build a full HTTP link to the code on GitHub or such like. This is sometimes a better option that including the code within the documentation itself. (E.g. may have stronger permission on it than the docs themselves.)
You write a little helper function in your conf.py file, and it does the rest.
What I really like about linkcode is that it creates links for enums, enum values, and data elements, which I could not get to be linked with viewcode.
I extended the link building code to use #:~:text= to cause the linked-to page to scroll to the text. Not perfect, as it will only scroll to the first instance, which may not always be correct, but likely 80~90% of the time it will be.
from urllib.parse import quote
def linkcode_resolve(domain, info):
# print(f"domain={domain}, info={info}")
if domain != 'py':
return None
if not info['module']:
return None
filename = quote(info['module'].replace('.', '/'))
if not filename.startswith("tests"):
filename = "src/" + filename
if "fullname" in info:
anchor = info["fullname"]
anchor = "#:~:text=" + quote(anchor.split(".")[-1])
else:
anchor = ""
# github
result = "https://<github>/<user>/<repo>/blob/master/%s.py%s" % (filename, anchor)
# print(result)
return result

Module variable documentation error

I get the following error while documenting a module variable json_class_index (See source), which does not have a docstring.
The generated documentation seems to be fine. What is a good fix?
reading sources... [100%] sanskrit_data_schema_common
/home/vvasuki/sanskrit_data/sanskrit_data/schema/common.py:docstring of sanskrit_data.schema.common.json_class_index:3: WARNING: Unexpected indentation.
/home/vvasuki/sanskrit_data/sanskrit_data/schema/common.py:docstring of sanskrit_data.schema.common.json_class_index:4: WARNING: Block quote ends without a blank line; unexpected unindent.
/home/vvasuki/sanskrit_data/sanskrit_data/schema/common.py:docstring of sanskrit_data.schema.common.json_class_index:7: WARNING: Unexpected indentation.
/home/vvasuki/sanskrit_data/sanskrit_data/schema/common.py:docstring of sanskrit_data.schema.common.json_class_index:8: WARNING: Inline strong start-string without end-string.
Edit:
PS: Note that removing the below docstring makes the error disappear, so it seems to be the thing to fix.
.. autodata:: json_class_index
:annotation: Maps jsonClass values to Python object names. Useful for (de)serialization. Updated using update_json_class_index() calls at the end of each module file (such as this one) whose classes may be serialized.
The warning messages indicate that the reStructuredText syntax of your docstrings is not valid and needs to be corrected.
Additionally your source code does not comply with PEP 8. Indentation should be 4 spaces, but your code uses 2, which might cause problems with Sphinx.
First make your code compliant with PEP 8 indentation.
Second, you must have two lines separating whatever precedes info field lists and the info field lists themselves.
Third, if the warnings persist, then look at the line numbers in the warnings—3, 4, 7, and 8—and the warnings themselves. It appears that the warnings correspond to this block of code:
#classmethod
def make_from_dict(cls, input_dict):
"""Defines *our* canonical way of constructing a JSON object from a dict.
All other deserialization methods should use this.
Note that this assumes that json_class_index is populated properly!
- ``from sanskrit_data.schema import *`` before using this should take care of it.
:param input_dict:
:return: A subclass of JsonObject
"""
Try this instead, post-PEP-8-ification, which should correct most of the warnings caused by faulty white space in your docstring:
#classmethod
def make_from_dict(cls, input_dict):
"""
Defines *our* canonical way of constructing a JSON object from a dict.
All other deserialization methods should use this.
Note that this assumes that json_class_index is populated properly!
- ``from sanskrit_data.schema import *`` before using this should take care of it.
:param input_dict:
:return: A subclass of JsonObject
"""
This style is acceptable according to PEP 257. The indentation is visually and vertically consistent, where the triple quotes vertically align with the left indentation. I think it's easier to read.
The fix was to add a docstring for the variable as follows:
#: Maps jsonClass values to Python object names. Useful for (de)serialization. Updated using update_json_class_index() calls at the end of each module file (such as this one) whose classes may be serialized.
json_class_index = {}

Is it possible to replace one directive with another one

I would like to create a substitution (or similar) that transforms one directive into another.
For example:
In our sphinx based documentation, we use Admonitions to create certain note and warning boxes.
However, if we use
.. note:: This is a Note
The title of the box is Note, and This is a Note becomes the first paragraph.
In contrast, this directive
.. admonition:: This is a Note
:class: note
produces a note box with the desired title.
To make it easier for other editors, I would like to create a substitution, that replaces the first with the second.
Is there anything this can be done with in sphinx?
Yes, it can be done. You have to add a custom directive to Sphinx. Create a Python module (like mydirectives.py next to conf.py) with the following:
import os
import os.path
import re
import subprocess
import docutils.core
import docutils.nodes
import docutils.parsers.rst
class AbstractDirective(docutils.parsers.rst.Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
option_spec = {}
final_argument_whitespace = False
node_class = docutils.nodes.container
def run(self):
self.assert_has_content()
text = '\n'.join(self.content)
admonition_node = self.node_class(rawsource=text)
self.state.nested_parse(self.content, self.content_offset,
admonition_node)
admonition_node.set_class("abstract")
return [admonition_node]
def setup(app):
app.add_directive('abstract', AbstractDirective)
There must be some way to add the title as well. Perhaps you need to add a
title node yourself. The documentation is lacking there, best look at the
source for
admonitions
and you will get a feel for the docutils.
With a custom text node you should be able to make up your own note directive.

Resources