diff --git a/.ruff.toml b/.ruff.toml index 5d38a3a01..03ade239c 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -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", diff --git a/sphinx/ext/intersphinx/_cli.py b/sphinx/ext/intersphinx/_cli.py index 25ec6ca7c..04ac28762 100644 --- a/sphinx/ext/intersphinx/_cli.py +++ b/sphinx/ext/intersphinx/_cli.py @@ -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) diff --git a/sphinx/ext/intersphinx/_load.py b/sphinx/ext/intersphinx/_load.py index 53324f710..b891d0d99 100644 --- a/sphinx/ext/intersphinx/_load.py +++ b/sphinx/ext/intersphinx/_load.py @@ -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 diff --git a/sphinx/ext/intersphinx/_resolve.py b/sphinx/ext/intersphinx/_resolve.py index a816a94f2..9387d1e10 100644 --- a/sphinx/ext/intersphinx/_resolve.py +++ b/sphinx/ext/intersphinx/_resolve.py @@ -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: diff --git a/sphinx/ext/intersphinx/_shared.py b/sphinx/ext/intersphinx/_shared.py index 36c73786f..44ad95623 100644 --- a/sphinx/ext/intersphinx/_shared.py +++ b/sphinx/ext/intersphinx/_shared.py @@ -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):