feature: extension(?): relative cross-references in docstrings, but resolved by the handler/extractor
Is your feature request related to a problem? Please describe.
I find myself repeatedly writing (mkdocs/mkdocstrings-formatted) documentation of the following form, where I have to reference other functions doing things that affect my current function:
# Google style code, not just documentation
from my.awesome.package import configuration
"""Do the thing.
...
Raises:
RuntimeError:
Obtaining the configuration via [`get_config`][my.awesome.package.configuration.get_config]
resulted in an error.
"""
# need to specify full path :(
i.e. where the link to myawesome.package.configuration.get_config needs to be spelled out in full. Generally this happens when things like enum.Enums and typing.NamedTuples come into play, and these get moved to separate packages but constantly must be referenced.
Describe the solution you'd like
mkdocstrings, the main benefactor of Griffe, and its parent system mkdocs, use Markdown, which by design is intended to be readable in its source form. Python comes with docstring support and a built-in help function, i.e. a place for documentation to be placed and a system to query it, so that it is easy to read and write documentation in its source form. My point being, the source form of the docstring should be readable.
Therefore, in (my best interpretation of) the spirit of Google's style guide and of Markdown's design rationale, I imagine the cleanest way to specify this cross-reference would be:
# global value declared here
from my.awesome.package import configuration
...
"""Do the thing.
...
Raises:
RuntimeError:
Obtaining the configuration via [`configuration.get_config`][]
resulted in an error.
"""
# link target resolved as per the global value above
i.e. that cross-references can be specified using local object names that the Python environment already knows about (here: the myawesome.package.configuration module via the configuration alias). This is very readable even with Python's help function (and in mkdocs-generated documentation as well, of course), and it is intuitively clear for any Python programmer how to change this cross-reference if a different object needs to be referenced, or if anything has been renamed or imported under a different name or whatnot. It is also based on information that Griffe probably already extracted when resolving type hints or the like, so Griffe (and thus mkdocs) could likely easily be taught to “understand“ such links.
When using this notation, there would only be a need to format cross-references of the form [`identifier`][] or [`dotted.path`][], i.e. where the link text and the link target are equal, and are both valid Python identifiers. I imagine that this is unambiguous enough to have a very low false-positive rate, so it should be “relatively safe“ to treat every such equal-text-and-target link which is also a valid Python identifier as a cross-reference to resolve this way.
From my understanding of the architecture of mkdocs + mkdocs-autorefs + mkdocstrings-python, I believe there are two parts to this proposal:
-
Griffe (or a Griffe extension) should check if the current docstring contains a Markdown cross-reference of the aforementioned type. If we detect such a reference, Griffe should either resolve it directly (using the names from the module it already knows about) or else add enough information (e.g. names of possible candidates) to some auxiliary structure to help resolve it later.
-
mkdocs-autorefs(or an extension) should resolve the unresolved link target using the auxiliary structure, if it hasn't been resolved yet.
Describe alternatives you've considered
I'm aware of mkdocstrings/python#27, mkdocstrings/python#28 and of analog-garage/mkdocstrings-python-xref. I can't speak to the quality of the code, but I find the proposed syntax for relative identifiers especially repulsive, in a this-is-no-longer-text-this-is-code sort of way. For me this is no better for reading or writing than using full package names.
I'm aware of mkdocstrings/autorefs#37. Again, I really detest the proposed syntax, as it goes against the spirit of having documentation readable and understandable even in source form.[^1] I also disagree with the notion that the current object . in a relative link should be the class/function/whatever that the docstring belongs to, because relative import syntax only considers packages and modules, and nested scopes use modifiers like nonlocal or global but otherwise unqualified identifiers. I expect forcing users to use unestablished-but-superficially-familiar syntax for actually-familiar things would also force them to constantly have to look up the syntax just to get it right, because they can't quickly tell if a ..ParentClass link in SubClass.method refers to the sibling class ParentClass the same module, or in the parent module, or even if the superclass is ParentClass itself.
(I'm also aware of @pawamoy's, @lmmx's and @oprypin's comments and viewpoints there. I'm hoping you will chime in on this proposal too, if only because I think the new limited scope and clear division of (software) responsibility could simplify that pull request quite significantly.)
(Also, I can't tell if the pull request is dead, or just on hold, and if your stances on that design have changed since then.)
[^1]: No, I don't find Python's relative import syntax really aesthetical either, and would not want it in user-facing non-code text. For what it's worth, Google's style guide also explicitly discourages its use, though for different reasons.