Skip to content

Commit 7578770

Browse files
authored
Apply qualname_overrides in more circumstances (#191)
1 parent 684834c commit 7578770

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

.vscode/settings.json

-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
{
22
"python.analysis.typeCheckingMode": "strict",
33
"python.testing.pytestArgs": ["-vv", "--color=yes"],
4-
"python.testing.unittestEnabled": false,
54
"python.testing.pytestEnabled": true,
6-
"python.terminal.activateEnvironment": false,
75
"[python]": {
86
"editor.defaultFormatter": "charliermarsh.ruff",
97
"editor.formatOnSave": true,

src/scanpydoc/elegant_typehints/__init__.py

+23
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ def x() -> Tuple[int, float]:
6868
from collections.abc import Callable
6969

7070
from sphinx.config import Config
71+
from docutils.nodes import TextElement, reference
72+
from sphinx.addnodes import pending_xref
7173
from sphinx.application import Sphinx
74+
from sphinx.environment import BuildEnvironment
7275

7376

7477
__all__ = [
@@ -113,6 +116,24 @@ class PickleableCallable:
113116
__call__ = property(lambda self: self.func)
114117

115118

119+
# https://www.sphinx-doc.org/en/master/extdev/event_callbacks.html#event-missing-reference
120+
def _last_resolve(
121+
app: Sphinx,
122+
env: BuildEnvironment,
123+
node: pending_xref,
124+
contnode: TextElement,
125+
) -> reference | None:
126+
if "sphinx.ext.intersphinx" not in app.extensions:
127+
return None
128+
129+
from sphinx.ext.intersphinx import resolve_reference_detect_inventory
130+
131+
if (qualname := qualname_overrides.get(node["reftarget"])) is None:
132+
return None
133+
node["reftarget"] = qualname
134+
return resolve_reference_detect_inventory(env, node, contnode)
135+
136+
116137
@_setup_sig
117138
def setup(app: Sphinx) -> dict[str, Any]:
118139
"""Patches :mod:`sphinx_autodoc_typehints` for a more elegant display."""
@@ -123,6 +144,8 @@ def setup(app: Sphinx) -> dict[str, Any]:
123144
app.add_config_value("qualname_overrides", default={}, rebuild="html")
124145
app.add_config_value("annotate_defaults", default=True, rebuild="html")
125146
app.connect("config-inited", _init_vars)
147+
# Add 1 to priority to run after sphinx.ext.intersphinx
148+
app.connect("missing-reference", _last_resolve, priority=501)
126149

127150
from ._formatting import typehints_formatter
128151

tests/test_elegant_typehints.py

+42
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@
1212

1313
import pytest
1414

15+
from scanpydoc.elegant_typehints import _last_resolve, qualname_overrides
1516
from scanpydoc.elegant_typehints._formatting import typehints_formatter
1617

1718

1819
if TYPE_CHECKING:
1920
from types import ModuleType
2021
from typing import Protocol
22+
from collections.abc import Generator
2123

2224
from sphinx.application import Sphinx
2325

@@ -32,6 +34,12 @@ def __call__( # noqa: D102
3234
NONE_RTYPE = ":rtype: :sphinx_autodoc_typehints_type:`\\:py\\:obj\\:\\`None\\``"
3335

3436

37+
@pytest.fixture(autouse=True)
38+
def _reset_qualname_overrides() -> Generator[None, None, None]:
39+
yield
40+
qualname_overrides.clear()
41+
42+
3543
@pytest.fixture
3644
def testmod(make_module: Callable[[str, str], ModuleType]) -> ModuleType:
3745
return make_module(
@@ -240,6 +248,40 @@ def fn_test(m: object) -> None: # pragma: no cover
240248
]
241249

242250

251+
def test_resolve(app: Sphinx) -> None:
252+
"""Test that qualname_overrides affects _last_resolve as expected."""
253+
from docutils.nodes import TextElement, reference
254+
from sphinx.addnodes import pending_xref
255+
from sphinx.ext.intersphinx import InventoryAdapter
256+
257+
app.setup_extension("sphinx.ext.intersphinx")
258+
259+
# Inventory contains documented name
260+
InventoryAdapter(app.env).main_inventory["py:class"] = {
261+
"test.Class": ("TestProj", "1", "https://x.com", "Class"),
262+
}
263+
# Node contains name from code
264+
node = pending_xref(refdomain="py", reftarget="testmod.Class", reftype="class")
265+
266+
resolved = _last_resolve(app, app.env, node, TextElement())
267+
assert isinstance(resolved, reference)
268+
assert resolved["refuri"] == "https://x.com"
269+
assert resolved["reftitle"] == "(in TestProj v1)"
270+
271+
272+
@pytest.mark.parametrize("qualname", ["testmod.Class", "nonexistent.Class"])
273+
def test_resolve_failure(app: Sphinx, qualname: str) -> None:
274+
from docutils.nodes import TextElement
275+
from sphinx.addnodes import pending_xref
276+
277+
app.setup_extension("sphinx.ext.intersphinx")
278+
node = pending_xref(refdomain="py", reftarget=qualname, reftype="class")
279+
280+
resolved = _last_resolve(app, app.env, node, TextElement())
281+
assert resolved is None
282+
assert node["reftarget"] == qualname_overrides.get(qualname, qualname)
283+
284+
243285
# These guys aren’t listed as classes in Python’s intersphinx index:
244286
@pytest.mark.parametrize(
245287
"annotation",

0 commit comments

Comments
 (0)