Enable automatic formatting for `sphinx/ext/intersphinx/` (#12970)

This commit is contained in:
Adam Turner 2024-10-04 17:08:30 +01:00 committed by GitHub
parent a404f3e66c
commit 3a066f2bbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 223 additions and 101 deletions

View File

@ -468,7 +468,6 @@ exclude = [
"sphinx/ext/imgconverter.py",
"sphinx/ext/imgmath.py",
"sphinx/ext/inheritance_diagram.py",
"sphinx/ext/intersphinx/*",
"sphinx/ext/linkcode.py",
"sphinx/ext/mathjax.py",
"sphinx/ext/napoleon/__init__.py",

View File

@ -10,9 +10,11 @@ from sphinx.ext.intersphinx._load import _fetch_inventory
def inspect_main(argv: list[str], /) -> int:
"""Debug functionality to print out an inventory"""
if len(argv) < 1:
print('Print out an inventory file.\n'
'Error: must specify local path or URL to an inventory file.',
file=sys.stderr)
print(
'Print out an inventory file.\n'
'Error: must specify local path or URL to an inventory file.',
file=sys.stderr,
)
return 1
class MockConfig:
@ -27,7 +29,7 @@ def inspect_main(argv: list[str], /) -> int:
target_uri='',
inv_location=filename,
config=MockConfig(), # type: ignore[arg-type]
srcdir='' # type: ignore[arg-type]
srcdir='', # type: ignore[arg-type]
)
for key in sorted(inv_data or {}):
print(key)

View File

@ -89,8 +89,10 @@ def validate_intersphinx_mapping(app: Sphinx, config: Config) -> None:
# ensure target URIs are non-empty and unique
if not uri or not isinstance(uri, str):
errors += 1
msg = __('Invalid target URI value `%r` in intersphinx_mapping[%r][0]. '
'Target URIs must be unique non-empty strings.')
msg = __(
'Invalid target URI value `%r` in intersphinx_mapping[%r][0]. '
'Target URIs must be unique non-empty strings.'
)
LOGGER.error(msg, uri, name)
del config.intersphinx_mapping[name]
continue
@ -105,9 +107,12 @@ def validate_intersphinx_mapping(app: Sphinx, config: Config) -> None:
continue
seen[uri] = name
if not isinstance(inv, tuple | list):
inv = (inv,)
# ensure inventory locations are None or non-empty
targets: list[InventoryLocation] = []
for target in (inv if isinstance(inv, (tuple | list)) else (inv,)):
for target in inv:
if target is None or target and isinstance(target, str):
targets.append(target)
else:
@ -143,9 +148,13 @@ def load_mappings(app: Sphinx) -> None:
projects = []
for name, (uri, locations) in intersphinx_mapping.values():
try:
project = _IntersphinxProject(name=name, target_uri=uri, locations=locations)
project = _IntersphinxProject(
name=name, target_uri=uri, locations=locations
)
except ValueError as err:
msg = __('An invalid intersphinx_mapping entry was added after normalisation.')
msg = __(
'An invalid intersphinx_mapping entry was added after normalisation.'
)
raise ConfigError(msg) from err
else:
projects.append(project)
@ -223,8 +232,11 @@ def _fetch_inventory_group(
or project.target_uri not in cache
or cache[project.target_uri][1] < cache_time
):
LOGGER.info(__("loading intersphinx inventory '%s' from %s ..."),
project.name, _get_safe_url(inv))
LOGGER.info(
__("loading intersphinx inventory '%s' from %s ..."),
project.name,
_get_safe_url(inv),
)
try:
invdata = _fetch_inventory(
@ -245,14 +257,21 @@ def _fetch_inventory_group(
if not failures:
pass
elif len(failures) < len(project.locations):
LOGGER.info(__('encountered some issues with some of the inventories,'
' but they had working alternatives:'))
LOGGER.info(
__(
'encountered some issues with some of the inventories,'
' but they had working alternatives:'
)
)
for fail in failures:
LOGGER.info(*fail)
else:
issues = '\n'.join(f[0] % f[1:] for f in failures)
LOGGER.warning(__('failed to reach any of the inventories '
'with the following issues:') + '\n' + issues)
LOGGER.warning(
__('failed to reach any of the inventories ' 'with the following issues:')
+ '\n'
+ issues
)
return updated
@ -267,7 +286,7 @@ def fetch_inventory(app: Sphinx, uri: InventoryURI, inv: str) -> Inventory:
def _fetch_inventory(
*, target_uri: InventoryURI, inv_location: str, config: Config, srcdir: Path,
*, target_uri: InventoryURI, inv_location: str, config: Config, srcdir: Path
) -> Inventory:
"""Fetch, parse and return an intersphinx inventory file."""
# both *target_uri* (base URI of the links to generate)
@ -282,8 +301,12 @@ def _fetch_inventory(
else:
f = open(path.join(srcdir, inv_location), 'rb') # NoQA: SIM115
except Exception as err:
err.args = ('intersphinx inventory %r not fetchable due to %s: %s',
inv_location, err.__class__, str(err))
err.args = (
'intersphinx inventory %r not fetchable due to %s: %s',
inv_location,
err.__class__,
str(err),
)
raise
try:
if hasattr(f, 'url'):
@ -295,17 +318,22 @@ def _fetch_inventory(
if target_uri in {
inv_location,
path.dirname(inv_location),
path.dirname(inv_location) + '/'
path.dirname(inv_location) + '/',
}:
target_uri = path.dirname(new_inv_location)
with f:
try:
invdata = InventoryFile.load(f, target_uri, posixpath.join)
except ValueError as exc:
raise ValueError('unknown or unsupported inventory version: %r' % exc) from exc
msg = f'unknown or unsupported inventory version: {exc!r}'
raise ValueError(msg) from exc
except Exception as err:
err.args = ('intersphinx inventory %r not readable due to %s: %s',
inv_location, err.__class__.__name__, str(err))
err.args = (
'intersphinx inventory %r not readable due to %s: %s',
inv_location,
err.__class__.__name__,
str(err),
)
raise
else:
return invdata
@ -373,9 +401,13 @@ def _read_from_url(url: str, *, config: Config) -> HTTPResponse:
:return: data read from resource described by *url*
:rtype: ``file``-like object
"""
r = requests.get(url, stream=True, timeout=config.intersphinx_timeout,
_user_agent=config.user_agent,
_tls_info=(config.tls_verify, config.tls_cacerts))
r = requests.get(
url,
stream=True,
timeout=config.intersphinx_timeout,
_user_agent=config.user_agent,
_tls_info=(config.tls_verify, config.tls_cacerts),
)
r.raise_for_status()
# For inv_location / new_inv_location

View File

@ -32,9 +32,13 @@ if TYPE_CHECKING:
from sphinx.util.typing import Inventory, InventoryItem, RoleFunction
def _create_element_from_result(domain: Domain, inv_name: InventoryName | None,
data: InventoryItem,
node: pending_xref, contnode: TextElement) -> nodes.reference:
def _create_element_from_result(
domain: Domain,
inv_name: InventoryName | None,
data: InventoryItem,
node: pending_xref,
contnode: TextElement,
) -> nodes.reference:
proj, version, uri, dispname = data
if '://' not in uri and node.get('refdoc'):
# get correct path in case of subdirectories
@ -51,8 +55,11 @@ def _create_element_from_result(domain: Domain, inv_name: InventoryName | None,
# use whatever title was given, but strip prefix
title = contnode.astext()
if inv_name is not None and title.startswith(inv_name + ':'):
newnode.append(contnode.__class__(title[len(inv_name) + 1:],
title[len(inv_name) + 1:]))
newnode.append(
contnode.__class__(
title[len(inv_name) + 1 :], title[len(inv_name) + 1 :]
)
)
else:
newnode.append(contnode)
else:
@ -62,10 +69,14 @@ def _create_element_from_result(domain: Domain, inv_name: InventoryName | None,
def _resolve_reference_in_domain_by_target(
inv_name: InventoryName | None, inventory: Inventory,
domain: Domain, objtypes: Iterable[str],
target: str,
node: pending_xref, contnode: TextElement) -> nodes.reference | None:
inv_name: InventoryName | None,
inventory: Inventory,
domain: Domain,
objtypes: Iterable[str],
target: str,
node: pending_xref,
contnode: TextElement,
) -> nodes.reference | None:
for objtype in objtypes:
if objtype not in inventory:
# Continue if there's nothing of this kind in the inventory
@ -79,19 +90,34 @@ def _resolve_reference_in_domain_by_target(
# * 'term': https://github.com/sphinx-doc/sphinx/issues/9291
# * 'label': https://github.com/sphinx-doc/sphinx/issues/12008
target_lower = target.lower()
insensitive_matches = list(filter(lambda k: k.lower() == target_lower,
inventory[objtype].keys()))
insensitive_matches = list(
filter(lambda k: k.lower() == target_lower, inventory[objtype].keys())
)
if len(insensitive_matches) > 1:
data_items = {inventory[objtype][match] for match in insensitive_matches}
data_items = {
inventory[objtype][match] for match in insensitive_matches
}
inv_descriptor = inv_name or 'main_inventory'
if len(data_items) == 1: # these are duplicates; relatively innocuous
LOGGER.debug(__("inventory '%s': duplicate matches found for %s:%s"),
inv_descriptor, objtype, target,
type='intersphinx', subtype='external', location=node)
LOGGER.debug(
__("inventory '%s': duplicate matches found for %s:%s"),
inv_descriptor,
objtype,
target,
type='intersphinx',
subtype='external',
location=node,
)
else:
LOGGER.warning(__("inventory '%s': multiple matches found for %s:%s"),
inv_descriptor, objtype, target,
type='intersphinx', subtype='external', location=node)
LOGGER.warning(
__("inventory '%s': multiple matches found for %s:%s"),
inv_descriptor,
objtype,
target,
type='intersphinx',
subtype='external',
location=node,
)
if insensitive_matches:
data = inventory[objtype][insensitive_matches[0]]
else:
@ -106,12 +132,16 @@ def _resolve_reference_in_domain_by_target(
return None
def _resolve_reference_in_domain(env: BuildEnvironment,
inv_name: InventoryName | None, inventory: Inventory,
honor_disabled_refs: bool,
domain: Domain, objtypes: Iterable[str],
node: pending_xref, contnode: TextElement,
) -> nodes.reference | None:
def _resolve_reference_in_domain(
env: BuildEnvironment,
inv_name: InventoryName | None,
inventory: Inventory,
honor_disabled_refs: bool,
domain: Domain,
objtypes: Iterable[str],
node: pending_xref,
contnode: TextElement,
) -> nodes.reference | None:
obj_types: dict[str, None] = {}.fromkeys(objtypes)
# we adjust the object types for backwards compatibility
@ -129,15 +159,16 @@ def _resolve_reference_in_domain(env: BuildEnvironment,
# now that the objtypes list is complete we can remove the disabled ones
if honor_disabled_refs:
disabled = set(env.config.intersphinx_disabled_reftypes)
obj_types = {obj_type: None
for obj_type in obj_types
if obj_type not in disabled}
obj_types = {
obj_type: None for obj_type in obj_types if obj_type not in disabled
}
objtypes = [*obj_types.keys()]
# without qualification
res = _resolve_reference_in_domain_by_target(inv_name, inventory, domain, objtypes,
node['reftarget'], node, contnode)
res = _resolve_reference_in_domain_by_target(
inv_name, inventory, domain, objtypes, node['reftarget'], node, contnode
)
if res is not None:
return res
@ -145,14 +176,19 @@ def _resolve_reference_in_domain(env: BuildEnvironment,
full_qualified_name = domain.get_full_qualified_name(node)
if full_qualified_name is None:
return None
return _resolve_reference_in_domain_by_target(inv_name, inventory, domain, objtypes,
full_qualified_name, node, contnode)
return _resolve_reference_in_domain_by_target(
inv_name, inventory, domain, objtypes, full_qualified_name, node, contnode
)
def _resolve_reference(env: BuildEnvironment,
inv_name: InventoryName | None, inventory: Inventory,
honor_disabled_refs: bool,
node: pending_xref, contnode: TextElement) -> nodes.reference | None:
def _resolve_reference(
env: BuildEnvironment,
inv_name: InventoryName | None,
inventory: Inventory,
honor_disabled_refs: bool,
node: pending_xref,
contnode: TextElement,
) -> nodes.reference | None:
# disabling should only be done if no inventory is given
honor_disabled_refs = honor_disabled_refs and inv_name is None
intersphinx_disabled_reftypes = env.config.intersphinx_disabled_reftypes
@ -163,13 +199,22 @@ def _resolve_reference(env: BuildEnvironment,
typ = node['reftype']
if typ == 'any':
for domain in env.domains.sorted():
if honor_disabled_refs and f'{domain.name}:*' in intersphinx_disabled_reftypes:
if (
honor_disabled_refs
and f'{domain.name}:*' in intersphinx_disabled_reftypes
):
continue
objtypes: Iterable[str] = domain.object_types.keys()
res = _resolve_reference_in_domain(env, inv_name, inventory,
honor_disabled_refs,
domain, objtypes,
node, contnode)
res = _resolve_reference_in_domain(
env,
inv_name,
inventory,
honor_disabled_refs,
domain,
objtypes,
node,
contnode,
)
if res is not None:
return res
return None
@ -184,20 +229,28 @@ def _resolve_reference(env: BuildEnvironment,
objtypes = domain.objtypes_for_role(typ) or ()
if not objtypes:
return None
return _resolve_reference_in_domain(env, inv_name, inventory,
honor_disabled_refs,
domain, objtypes,
node, contnode)
return _resolve_reference_in_domain(
env,
inv_name,
inventory,
honor_disabled_refs,
domain,
objtypes,
node,
contnode,
)
def inventory_exists(env: BuildEnvironment, inv_name: InventoryName) -> bool:
return inv_name in InventoryAdapter(env).named_inventory
def resolve_reference_in_inventory(env: BuildEnvironment,
inv_name: InventoryName,
node: pending_xref, contnode: TextElement,
) -> nodes.reference | None:
def resolve_reference_in_inventory(
env: BuildEnvironment,
inv_name: InventoryName,
node: pending_xref,
contnode: TextElement,
) -> nodes.reference | None:
"""Attempt to resolve a missing reference via intersphinx references.
Resolution is tried in the given inventory with the target as is.
@ -205,26 +258,39 @@ def resolve_reference_in_inventory(env: BuildEnvironment,
Requires ``inventory_exists(env, inv_name)``.
"""
assert inventory_exists(env, inv_name)
return _resolve_reference(env, inv_name, InventoryAdapter(env).named_inventory[inv_name],
False, node, contnode)
return _resolve_reference(
env,
inv_name,
InventoryAdapter(env).named_inventory[inv_name],
False,
node,
contnode,
)
def resolve_reference_any_inventory(env: BuildEnvironment,
honor_disabled_refs: bool,
node: pending_xref, contnode: TextElement,
) -> nodes.reference | None:
def resolve_reference_any_inventory(
env: BuildEnvironment,
honor_disabled_refs: bool,
node: pending_xref,
contnode: TextElement,
) -> nodes.reference | None:
"""Attempt to resolve a missing reference via intersphinx references.
Resolution is tried with the target as is in any inventory.
"""
return _resolve_reference(env, None, InventoryAdapter(env).main_inventory,
honor_disabled_refs,
node, contnode)
return _resolve_reference(
env,
None,
InventoryAdapter(env).main_inventory,
honor_disabled_refs,
node,
contnode,
)
def resolve_reference_detect_inventory(env: BuildEnvironment,
node: pending_xref, contnode: TextElement,
) -> nodes.reference | None:
def resolve_reference_detect_inventory(
env: BuildEnvironment, node: pending_xref, contnode: TextElement
) -> nodes.reference | None:
"""Attempt to resolve a missing reference via intersphinx references.
Resolution is tried first with the target as is in any inventory.
@ -250,8 +316,9 @@ def resolve_reference_detect_inventory(env: BuildEnvironment,
return res_inv
def missing_reference(app: Sphinx, env: BuildEnvironment, node: pending_xref,
contnode: TextElement) -> nodes.reference | None:
def missing_reference(
app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: TextElement
) -> nodes.reference | None:
"""Attempt to resolve a missing reference via intersphinx references."""
return resolve_reference_detect_inventory(env, node, contnode)
@ -263,7 +330,11 @@ class IntersphinxDispatcher(CustomReSTDispatcher):
"""
def role(
self, role_name: str, language_module: ModuleType, lineno: int, reporter: Reporter,
self,
role_name: str,
language_module: ModuleType,
lineno: int,
reporter: Reporter,
) -> tuple[RoleFunction, list[system_message]]:
if len(role_name) > 9 and role_name.startswith(('external:', 'external+')):
return IntersphinxRole(role_name), []
@ -461,7 +532,9 @@ class IntersphinxRole(SphinxRole):
except ExtensionError:
return False
def invoke_role(self, role: tuple[str, str]) -> tuple[list[Node], list[system_message]]:
def invoke_role(
self, role: tuple[str, str]
) -> tuple[list[Node], list[system_message]]:
"""Invoke the role described by a ``(domain, role name)`` pair."""
_deprecation_warning(
__name__, f'{self.__class__.__name__}.invoke_role', '', remove=(9, 0)
@ -471,8 +544,15 @@ class IntersphinxRole(SphinxRole):
role_func = domain.role(role[1])
assert role_func is not None
return role_func(':'.join(role), self.rawtext, self.text, self.lineno,
self.inliner, self.options, self.content)
return role_func(
':'.join(role),
self.rawtext,
self.text,
self.lineno,
self.inliner,
self.options,
self.content,
)
else:
return [], []
@ -493,13 +573,20 @@ class IntersphinxRoleResolver(ReferencesResolver):
inv_name = node['inventory']
if inv_name is not None:
assert inventory_exists(self.env, inv_name)
newnode = resolve_reference_in_inventory(self.env, inv_name, node, contnode)
newnode = resolve_reference_in_inventory(
self.env, inv_name, node, contnode
)
else:
newnode = resolve_reference_any_inventory(self.env, False, node, contnode)
newnode = resolve_reference_any_inventory(
self.env, False, node, contnode
)
if newnode is None:
typ = node['reftype']
msg = (__('external %s:%s reference target not found: %s') %
(node['refdomain'], typ, node['reftarget']))
msg = __('external %s:%s reference target not found: %s') % (
node['refdomain'],
typ,
node['reftarget'],
)
LOGGER.warning(msg, location=node, type='ref', subtype=typ)
node.replace_self(contnode)
else:

View File

@ -54,7 +54,7 @@ class _IntersphinxProject:
'locations': 'A tuple of local or remote targets containing '
'the inventory data to fetch. '
'None indicates the default inventory file name.',
}
} # fmt: skip
def __init__(
self,
@ -83,10 +83,12 @@ class _IntersphinxProject:
object.__setattr__(self, 'locations', tuple(locations))
def __repr__(self) -> str:
return (f'{self.__class__.__name__}('
f'name={self.name!r}, '
f'target_uri={self.target_uri!r}, '
f'locations={self.locations!r})')
return (
f'{self.__class__.__name__}('
f'name={self.name!r}, '
f'target_uri={self.target_uri!r}, '
f'locations={self.locations!r})'
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, _IntersphinxProject):