diff --git a/src/sphinx_autodoc_typehints/__init__.py b/src/sphinx_autodoc_typehints/__init__.py index 824f458..fbd57ce 100644 --- a/src/sphinx_autodoc_typehints/__init__.py +++ b/src/sphinx_autodoc_typehints/__init__.py @@ -169,6 +169,18 @@ def format_internal_tuple(t: tuple[Any, ...], config: Config) -> str: return f"({', '.join(fmt)})" +def fixup_module_name(config: Config, module: str) -> str: + if getattr(config, "typehints_fixup_module_name", None): + module = config.typehints_fixup_module_name(module) + + if module == "typing_extensions": + module = "typing" + + if module == "_io": + module = "io" + return module + + def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PLR0911, PLR0912, PLR0915, PLR0914 """ Format the annotation. @@ -204,13 +216,7 @@ def format_annotation(annotation: Any, config: Config) -> str: # noqa: C901, PL except ValueError: return str(annotation).strip("'") - # Redirect all typing_extensions types to the stdlib typing module - if module == "typing_extensions": - module = "typing" - - if module == "_io": - module = "io" - + module = fixup_module_name(config, module) full_name = f"{module}.{class_name}" if module != "builtins" else class_name fully_qualified: bool = getattr(config, "typehints_fully_qualified", False) prefix = "" if fully_qualified or full_name == class_name else "~" @@ -967,6 +973,7 @@ def setup(app: Sphinx) -> dict[str, bool]: app.add_config_value("typehints_formatter", None, "env") app.add_config_value("typehints_use_signature", False, "env") # noqa: FBT003 app.add_config_value("typehints_use_signature_return", False, "env") # noqa: FBT003 + app.add_config_value("typehints_fixup_module_name", None, "env") app.add_role("sphinx_autodoc_typehints_type", sphinx_autodoc_typehints_type_role) app.connect("env-before-read-docs", validate_config) # config may be changed after “config-inited” event app.connect("autodoc-process-signature", process_signature) diff --git a/tests/roots/test-dummy/export_module.py b/tests/roots/test-dummy/export_module.py new file mode 100644 index 0000000..0fa35a8 --- /dev/null +++ b/tests/roots/test-dummy/export_module.py @@ -0,0 +1,5 @@ +from __future__ import annotations + +from wrong_module_path import A, f + +__all__ = ["A", "f"] diff --git a/tests/roots/test-dummy/future_annotations.rst b/tests/roots/test-dummy/future_annotations.rst index 3d774cb..97c2bce 100644 --- a/tests/roots/test-dummy/future_annotations.rst +++ b/tests/roots/test-dummy/future_annotations.rst @@ -1,3 +1,5 @@ +:orphan: + Dummy Module ============ diff --git a/tests/roots/test-dummy/simple.rst b/tests/roots/test-dummy/simple.rst index 3634707..c920b7d 100644 --- a/tests/roots/test-dummy/simple.rst +++ b/tests/roots/test-dummy/simple.rst @@ -1,3 +1,5 @@ +:orphan: + Simple Module ============= diff --git a/tests/roots/test-dummy/simple_default_role.rst b/tests/roots/test-dummy/simple_default_role.rst index c3148a7..186f1a8 100644 --- a/tests/roots/test-dummy/simple_default_role.rst +++ b/tests/roots/test-dummy/simple_default_role.rst @@ -1,3 +1,5 @@ +:orphan: + Simple Module ============= diff --git a/tests/roots/test-dummy/simple_no_use_rtype.rst b/tests/roots/test-dummy/simple_no_use_rtype.rst index 00b2d61..f7b3034 100644 --- a/tests/roots/test-dummy/simple_no_use_rtype.rst +++ b/tests/roots/test-dummy/simple_no_use_rtype.rst @@ -1,3 +1,5 @@ +:orphan: + Simple Module ============= diff --git a/tests/roots/test-dummy/without_complete_typehints.rst b/tests/roots/test-dummy/without_complete_typehints.rst index 2e7a204..4eb63ce 100644 --- a/tests/roots/test-dummy/without_complete_typehints.rst +++ b/tests/roots/test-dummy/without_complete_typehints.rst @@ -1,3 +1,5 @@ +:orphan: + Simple Module ============= diff --git a/tests/roots/test-dummy/wrong_module_path.py b/tests/roots/test-dummy/wrong_module_path.py new file mode 100644 index 0000000..e5542ed --- /dev/null +++ b/tests/roots/test-dummy/wrong_module_path.py @@ -0,0 +1,9 @@ +from __future__ import annotations + + +class A: + pass + + +def f() -> A: + pass diff --git a/tests/roots/test-dummy/wrong_module_path.rst b/tests/roots/test-dummy/wrong_module_path.rst new file mode 100644 index 0000000..04b28b0 --- /dev/null +++ b/tests/roots/test-dummy/wrong_module_path.rst @@ -0,0 +1,5 @@ +:orphan: + +.. class:: export_module.A + +.. autofunction:: export_module.f diff --git a/tests/test_sphinx_autodoc_typehints.py b/tests/test_sphinx_autodoc_typehints.py index 93df0fe..94c1c86 100644 --- a/tests/test_sphinx_autodoc_typehints.py +++ b/tests/test_sphinx_autodoc_typehints.py @@ -1123,3 +1123,26 @@ def test_default_annotation_without_typehints(app: SphinxTestApp, status: String "str" """ assert text_contents == dedent(expected_contents) + + +@pytest.mark.sphinx("text", testroot="dummy") +@patch("sphinx.writers.text.MAXWIDTH", 2000) +def test_wrong_module_path(app: SphinxTestApp, status: StringIO, warning: StringIO) -> None: + set_python_path() + + app.config.master_doc = "wrong_module_path" # create flag + app.config.default_role = "literal" + app.config.nitpicky = True + app.config.nitpick_ignore = {("py:data", "typing.Optional")} + + def fixup_module_name(mod: str) -> str: + if not mod.startswith("wrong_module_path"): + return mod + return "export_module" + mod.removeprefix("wrong_module_path") + + app.config.suppress_warnings = ["config.cache"] + app.config.typehints_fixup_module_name = fixup_module_name + app.build() + + assert "build succeeded" in status.getvalue() # Build succeeded + assert not warning.getvalue().strip()