Skip to content

Commit

Permalink
Add module name rewrite configuration option
Browse files Browse the repository at this point in the history
This adds a configuration option that allows the user to rewrite module names if
needed. Resolves #473. Note that we also rewrite the names of `_io` and
`typing_extensions` internally. One benefit of this hook is that if other
similar rewrites are needed, users will be able to immediately add them without
having to patch sphinx_autodoc_typehints itself.

One disadvantage is that by default, having a function in the config prevents
caching. I think this can be handled with the following slightly inelegant hack:

```py
class ModuleNameRewriteHook:
   version: int

   def __eq__(self, other):
      return type(self) == type(other) and self.version == other.version

   def __init__(self):
      self.version = 2

   def __call__(self, module):
      # logic here
      # Make sure to bump version if you edit this so that sphinx will rerun.
      return module

typehints_fixup_module_name = ModuleNameRewriteHook
```

See sphinx-doc/sphinx#12300.
  • Loading branch information
hoodmane committed Aug 28, 2024
1 parent fdad0d7 commit 8067650
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 7 deletions.
21 changes: 14 additions & 7 deletions src/sphinx_autodoc_typehints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 config.typehints_fixup_module_name:
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.
Expand Down Expand Up @@ -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 "~"
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions tests/roots/test-dummy/export_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from __future__ import annotations

from wrong_module_path import A, f

__all__ = ["A", "f"]
9 changes: 9 additions & 0 deletions tests/roots/test-dummy/wrong_module_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from __future__ import annotations


class A:
pass


def f() -> A:
pass
3 changes: 3 additions & 0 deletions tests/roots/test-dummy/wrong_module_path.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.. class:: export_module.A

.. autofunction:: export_module.f
1 change: 1 addition & 0 deletions tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,7 @@ def test_integration(
(Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
app.config.__dict__.update(configs[conf_run])
app.config.__dict__.update(val.OPTIONS)
app.config.nitpicky = True
monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
app.build()
assert "build succeeded" in status.getvalue() # Build succeeded
Expand Down
22 changes: 22 additions & 0 deletions tests/test_sphinx_autodoc_typehints.py
Original file line number Diff line number Diff line change
Expand Up @@ -1123,3 +1123,25 @@ 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

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()

0 comments on commit 8067650

Please sign in to comment.