Skip to content

Commit 2287e4a

Browse files
committed
Add intersphinx_disabled_domains
Fixes sphinx-doc#2068 Replaces sphinx-doc#8981
1 parent 2ee3ef6 commit 2287e4a

File tree

5 files changed

+127
-33
lines changed

5 files changed

+127
-33
lines changed

CHANGES

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ Features added
2222
* #9691: C, added new info-field ``retval``
2323
for :rst:dir:`c:function` and :rst:dir:`c:macro`.
2424
* C++, added new info-field ``retval`` for :rst:dir:`cpp:function`.
25+
* #2068, add :confval:`intersphinx_disabled_domains` for disabling
26+
interphinx resolution of cross-references in specific domains when they
27+
do not have an explicit inventory specification.
28+
``sphinx-quickstart`` will insert
29+
``intersphinx_disabled_domains = ['std']`` in its generated ``conf.py``.
2530

2631
Bugs fixed
2732
----------

doc/usage/extensions/intersphinx.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,25 @@ linking:
148148
exception is raised if the server has not issued a response for timeout
149149
seconds.
150150

151+
.. confval:: intersphinx_disabled_domains
152+
153+
.. versionadded:: 4.2
154+
155+
A list of strings being the name of a domain, or the special name ``all``.
156+
When a cross-reference without an explicit inventory specification is being
157+
resolve by intersphinx, skip resolution if either the domain of the
158+
cross-reference is in this list or the special name ``all`` is in the list.
159+
160+
For example, with ``intersphinx_disabled_domains = ['std']`` a cross-reference
161+
``:doc:`installation``` will not be attempted to be resolved by intersphinx, but
162+
``:doc:`otherbook:installation``` will be attempted to be resolved in the
163+
inventory named ``otherbook`` in :confval:`intersphinx_mapping`.
164+
At the same time, all cross-references generated in, e.g., Python, declarations
165+
will still be attempted to be resolved by intersphinx.
166+
167+
If ``all`` is in the list of domains, then no references without an explicit
168+
inventory will be resolved by intersphinx.
169+
151170

152171
Showing all links of an Intersphinx mapping file
153172
------------------------------------------------

sphinx/ext/intersphinx.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -351,11 +351,20 @@ def _resolve_reference_in_domain(inv_name: Optional[str], inventory: Inventory,
351351

352352

353353
def _resolve_reference(env: BuildEnvironment, inv_name: Optional[str], inventory: Inventory,
354+
honor_disabled_domains: bool,
354355
node: pending_xref, contnode: TextElement) -> Optional[Element]:
355-
# figure out which object types we should look for
356+
# disabling should only be done if no inventory is given
357+
honor_disabled_domains = honor_disabled_domains and inv_name is None
358+
359+
if honor_disabled_domains and 'all' in env.config.intersphinx_disabled_domains:
360+
return None
361+
356362
typ = node['reftype']
357363
if typ == 'any':
358364
for domain_name, domain in env.domains.items():
365+
if honor_disabled_domains \
366+
and domain_name in env.config.intersphinx_disabled_domains:
367+
continue
359368
objtypes = list(domain.object_types)
360369
res = _resolve_reference_in_domain(inv_name, inventory,
361370
domain, objtypes,
@@ -368,6 +377,9 @@ def _resolve_reference(env: BuildEnvironment, inv_name: Optional[str], inventory
368377
if not domain_name:
369378
# only objects in domains are in the inventory
370379
return None
380+
if honor_disabled_domains \
381+
and domain_name in env.config.intersphinx_disabled_domains:
382+
return None
371383
domain = env.get_domain(domain_name)
372384
objtypes = domain.objtypes_for_role(typ)
373385
if not objtypes:
@@ -383,8 +395,8 @@ def inventory_exists(env: BuildEnvironment, inv_name: str) -> bool:
383395

384396
def resolve_reference_in_inventory(env: BuildEnvironment,
385397
inv_name: str,
386-
node: pending_xref,
387-
contnode: TextElement) -> Optional[Element]:
398+
node: pending_xref, contnode: TextElement
399+
) -> Optional[Element]:
388400
"""Attempt to resolve a missing reference via intersphinx references.
389401
390402
Resolution is tried in the given inventory with the target as is.
@@ -393,22 +405,26 @@ def resolve_reference_in_inventory(env: BuildEnvironment,
393405
"""
394406
assert inventory_exists(env, inv_name)
395407
return _resolve_reference(env, inv_name, InventoryAdapter(env).named_inventory[inv_name],
396-
node, contnode)
408+
False, node, contnode)
397409

398410

399411
def resolve_reference_any_inventory(env: BuildEnvironment,
400-
node: pending_xref,
401-
contnode: TextElement) -> Optional[Element]:
412+
honor_disabled_domains: bool,
413+
node: pending_xref, contnode: TextElement
414+
) -> Optional[Element]:
402415
"""Attempt to resolve a missing reference via intersphinx references.
403416
404417
Resolution is tried with the target as is in any inventory.
405418
"""
406-
return _resolve_reference(env, None, InventoryAdapter(env).main_inventory, node, contnode)
419+
return _resolve_reference(env, None, InventoryAdapter(env).main_inventory,
420+
honor_disabled_domains,
421+
node, contnode)
407422

408423

409424
def resolve_reference_detect_inventory(env: BuildEnvironment,
410-
node: pending_xref,
411-
contnode: TextElement) -> Optional[Element]:
425+
honor_disabled_domains: bool,
426+
node: pending_xref, contnode: TextElement
427+
) -> Optional[Element]:
412428
"""Attempt to resolve a missing reference via intersphinx references.
413429
414430
Resolution is tried first with the target as is in any inventory.
@@ -418,7 +434,7 @@ def resolve_reference_detect_inventory(env: BuildEnvironment,
418434
"""
419435

420436
# ordinary direct lookup, use data as is
421-
res = resolve_reference_any_inventory(env, node, contnode)
437+
res = resolve_reference_any_inventory(env, honor_disabled_domains, node, contnode)
422438
if res is not None:
423439
return res
424440

@@ -439,7 +455,7 @@ def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref,
439455
contnode: TextElement) -> Optional[Element]:
440456
"""Attempt to resolve a missing reference via intersphinx references."""
441457

442-
return resolve_reference_detect_inventory(env, node, contnode)
458+
return resolve_reference_detect_inventory(env, True, node, contnode)
443459

444460

445461
def normalize_intersphinx_mapping(app: Sphinx, config: Config) -> None:
@@ -470,6 +486,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
470486
app.add_config_value('intersphinx_mapping', {}, True)
471487
app.add_config_value('intersphinx_cache_limit', 5, False)
472488
app.add_config_value('intersphinx_timeout', None, False)
489+
app.add_config_value('intersphinx_disabled_domains', [], True)
473490
app.connect('config-inited', normalize_intersphinx_mapping, priority=800)
474491
app.connect('builder-inited', load_mappings)
475492
app.connect('missing-reference', missing_reference)

sphinx/templates/quickstart/conf.py_t

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ html_static_path = ['{{ dot }}static']
108108
intersphinx_mapping = {
109109
'python': ('https://docs.python.org/3', None),
110110
}
111+
112+
# Prevent accidental intersphinx resolution for labels, documents, and other
113+
# basic cross-references.
114+
intersphinx_disabled_domains = ['std']
111115
{%- endif %}
112116
{%- if 'sphinx.ext.todo' in extensions %}
113117

tests/test_ext_intersphinx.py

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ def reference_check(app, *args, **kwds):
4242
return missing_reference(app, app.env, node, contnode)
4343

4444

45+
def set_config(app, mapping):
46+
app.config.intersphinx_mapping = mapping
47+
app.config.intersphinx_cache_limit = 0
48+
app.config.intersphinx_disabled_domains = []
49+
50+
4551
@mock.patch('sphinx.ext.intersphinx.InventoryFile')
4652
@mock.patch('sphinx.ext.intersphinx._read_from_url')
4753
def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status, warning):
@@ -90,13 +96,12 @@ def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status,
9096
def test_missing_reference(tempdir, app, status, warning):
9197
inv_file = tempdir / 'inventory'
9298
inv_file.write_bytes(inventory_v2)
93-
app.config.intersphinx_mapping = {
99+
set_config(app, {
94100
'https://docs.python.org/': inv_file,
95101
'py3k': ('https://docs.python.org/py3k/', inv_file),
96102
'py3krel': ('py3k', inv_file), # relative path
97103
'py3krelparent': ('../../py3k', inv_file), # relative path, parent dir
98-
}
99-
app.config.intersphinx_cache_limit = 0
104+
})
100105

101106
# load the inventory and check if it's done correctly
102107
normalize_intersphinx_mapping(app, app.config)
@@ -169,10 +174,9 @@ def test_missing_reference(tempdir, app, status, warning):
169174
def test_missing_reference_pydomain(tempdir, app, status, warning):
170175
inv_file = tempdir / 'inventory'
171176
inv_file.write_bytes(inventory_v2)
172-
app.config.intersphinx_mapping = {
177+
set_config(app, {
173178
'https://docs.python.org/': inv_file,
174-
}
175-
app.config.intersphinx_cache_limit = 0
179+
})
176180

177181
# load the inventory and check if it's done correctly
178182
normalize_intersphinx_mapping(app, app.config)
@@ -210,10 +214,9 @@ def test_missing_reference_pydomain(tempdir, app, status, warning):
210214
def test_missing_reference_stddomain(tempdir, app, status, warning):
211215
inv_file = tempdir / 'inventory'
212216
inv_file.write_bytes(inventory_v2)
213-
app.config.intersphinx_mapping = {
217+
set_config(app, {
214218
'cmd': ('https://docs.python.org/', inv_file),
215-
}
216-
app.config.intersphinx_cache_limit = 0
219+
})
217220

218221
# load the inventory and check if it's done correctly
219222
normalize_intersphinx_mapping(app, app.config)
@@ -242,10 +245,9 @@ def test_missing_reference_stddomain(tempdir, app, status, warning):
242245
def test_missing_reference_cppdomain(tempdir, app, status, warning):
243246
inv_file = tempdir / 'inventory'
244247
inv_file.write_bytes(inventory_v2)
245-
app.config.intersphinx_mapping = {
248+
set_config(app, {
246249
'https://docs.python.org/': inv_file,
247-
}
248-
app.config.intersphinx_cache_limit = 0
250+
})
249251

250252
# load the inventory and check if it's done correctly
251253
normalize_intersphinx_mapping(app, app.config)
@@ -269,10 +271,9 @@ def test_missing_reference_cppdomain(tempdir, app, status, warning):
269271
def test_missing_reference_jsdomain(tempdir, app, status, warning):
270272
inv_file = tempdir / 'inventory'
271273
inv_file.write_bytes(inventory_v2)
272-
app.config.intersphinx_mapping = {
274+
set_config(app, {
273275
'https://docs.python.org/': inv_file,
274-
}
275-
app.config.intersphinx_cache_limit = 0
276+
})
276277

277278
# load the inventory and check if it's done correctly
278279
normalize_intersphinx_mapping(app, app.config)
@@ -291,14 +292,63 @@ def test_missing_reference_jsdomain(tempdir, app, status, warning):
291292
assert rn.astext() == 'baz()'
292293

293294

295+
def test_missing_reference_disabled_domain(tempdir, app, status, warning):
296+
inv_file = tempdir / 'inventory'
297+
inv_file.write_bytes(inventory_v2)
298+
set_config(app, {
299+
'inv': ('https://docs.python.org/', inv_file),
300+
})
301+
302+
# load the inventory and check if it's done correctly
303+
normalize_intersphinx_mapping(app, app.config)
304+
load_mappings(app)
305+
306+
def case(std_without, std_with, py_without, py_with):
307+
def assert_(rn, expected):
308+
if expected is None:
309+
assert rn is None
310+
else:
311+
assert rn.astext() == expected
312+
313+
kwargs = {}
314+
315+
node, contnode = fake_node('std', 'doc', 'docname', 'docname', **kwargs)
316+
rn = missing_reference(app, app.env, node, contnode)
317+
assert_(rn, std_without)
318+
319+
node, contnode = fake_node('std', 'doc', 'inv:docname', 'docname', **kwargs)
320+
rn = missing_reference(app, app.env, node, contnode)
321+
assert_(rn, std_with)
322+
323+
# an arbitrary ref in another domain
324+
node, contnode = fake_node('py', 'func', 'module1.func', 'func()', **kwargs)
325+
rn = missing_reference(app, app.env, node, contnode)
326+
assert_(rn, py_without)
327+
328+
node, contnode = fake_node('py', 'func', 'inv:module1.func', 'func()', **kwargs)
329+
rn = missing_reference(app, app.env, node, contnode)
330+
assert_(rn, py_with)
331+
332+
# the base case, everything should resolve
333+
assert app.config.intersphinx_disabled_domains == []
334+
case('docname', 'docname', 'func()', 'func()')
335+
336+
# disabled one domain
337+
app.config.intersphinx_disabled_domains = ['std']
338+
case(None, 'docname', 'func()', 'func()')
339+
340+
# disabled all domains
341+
app.config.intersphinx_disabled_domains = ['all']
342+
case(None, 'docname', None, 'func()')
343+
344+
294345
@pytest.mark.xfail(os.name != 'posix', reason="Path separator mismatch issue")
295346
def test_inventory_not_having_version(tempdir, app, status, warning):
296347
inv_file = tempdir / 'inventory'
297348
inv_file.write_bytes(inventory_v2_not_having_version)
298-
app.config.intersphinx_mapping = {
349+
set_config(app, {
299350
'https://docs.python.org/': inv_file,
300-
}
301-
app.config.intersphinx_cache_limit = 0
351+
})
302352

303353
# load the inventory and check if it's done correctly
304354
normalize_intersphinx_mapping(app, app.config)
@@ -318,16 +368,15 @@ def test_load_mappings_warnings(tempdir, app, status, warning):
318368
"""
319369
inv_file = tempdir / 'inventory'
320370
inv_file.write_bytes(inventory_v2)
321-
app.config.intersphinx_mapping = {
371+
set_config(app, {
322372
'https://docs.python.org/': inv_file,
323373
'py3k': ('https://docs.python.org/py3k/', inv_file),
324374
'repoze.workflow': ('http://docs.repoze.org/workflow/', inv_file),
325375
'django-taggit': ('http://django-taggit.readthedocs.org/en/latest/',
326376
inv_file),
327377
12345: ('http://www.sphinx-doc.org/en/stable/', inv_file),
328-
}
378+
})
329379

330-
app.config.intersphinx_cache_limit = 0
331380
# load the inventory and check if it's done correctly
332381
normalize_intersphinx_mapping(app, app.config)
333382
load_mappings(app)
@@ -337,7 +386,7 @@ def test_load_mappings_warnings(tempdir, app, status, warning):
337386
def test_load_mappings_fallback(tempdir, app, status, warning):
338387
inv_file = tempdir / 'inventory'
339388
inv_file.write_bytes(inventory_v2)
340-
app.config.intersphinx_cache_limit = 0
389+
set_config(app, {})
341390

342391
# connect to invalid path
343392
app.config.intersphinx_mapping = {

0 commit comments

Comments
 (0)