Repository: sphinx-doc/sphinx

Issue:
Sphinx autosummary modules should return module names without path
**Is your feature request related to a problem? Please describe.**
We are using ``sphinx.ext.autosummary`` with ``_templates`` to create new files for each class, attribute, function.
This works perfectly for the above mentioned, where the template file contains (e.g. just for ``classes``):

```rst
{% if classes %}
.. automodule:: {{ fullname }}
.. currentmodule:: {{ fullname }}

.. rubric:: Classes
.. autosummary::
    :toctree:
    :nosignatures:
    {% for class in classes %}
    {{ class }}
    {% endfor %}
{% endif %}
```

Note that we are setting with ``.. currentmodule:: {{ fullname }}`` the correct path to the current file and then document each class with: ``{% for class in classes %}``. This works, because ``classes`` returns the class names without prefix (the path contained in ``fullname``).

For modules, on the other hand, the approach doesn't work:

```rst
{% if modules %}
.. automodule:: {{ fullname }}
.. currentmodule:: {{ fullname }}
.. autosummary::
   :toctree:
   :recursive:
   :template: module.rst
   {% for module in modules %}
   {{ module }}
   {% endfor %}
{% endif %}
```

The problem is here that ``modules`` returns a list of modules with the **full path**. 

**Describe the solution you'd like**
Have ``modules`` return only the module name without prefix (full path)

**Additional context**
We use Sphinx to document our toolbox [do-mpc](www.do-mpc.com)



Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 7b4cd5848..a77bb761d 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,6 +4,9 @@ Release 7.4.6 (in development)
 Bugs fixed
 ----------
 
+* #12859, #9743, #12609: autosummary: Do not add the package prefix when
+  generating autosummary directives for modules within a package.
+  Patch by Adam Turner.
 
 Release 7.4.5 (released Jul 16, 2024)
 =====================================
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index d434d39f8..f4b0df04e 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -330,10 +330,6 @@ def generate_autosummary_content(
                     doc, app, obj, {'module'}, imported=True
                 )
                 skip += all_imported_modules
-                imported_modules = [name + '.' + modname for modname in imported_modules]
-                all_imported_modules = [
-                    name + '.' + modname for modname in all_imported_modules
-                ]
                 public_members = getall(obj)
             else:
                 imported_modules, all_imported_modules = [], []
@@ -476,21 +472,22 @@ def _get_modules(
         if modname in skip:
             # module was overwritten in __init__.py, so not accessible
             continue
-        fullname = name + '.' + modname
+        fullname = f'{name}.{modname}'
         try:
             module = import_module(fullname)
-            if module and hasattr(module, '__sphinx_mock__'):
-                continue
         except ImportError:
             pass
+        else:
+            if module and hasattr(module, '__sphinx_mock__'):
+                continue
 
-        items.append(fullname)
+        items.append(modname)
         if public_members is not None:
             if modname in public_members:
-                public.append(fullname)
+                public.append(modname)
         else:
             if not modname.startswith('_'):
-                public.append(fullname)
+                public.append(modname)
     return public, items
 
 
diff --git a/sphinx/ext/autosummary/templates/autosummary/module.rst b/sphinx/ext/autosummary/templates/autosummary/module.rst
index e74c012f4..3ff0de9ed 100644
--- a/sphinx/ext/autosummary/templates/autosummary/module.rst
+++ b/sphinx/ext/autosummary/templates/autosummary/module.rst
@@ -3,7 +3,7 @@
 .. automodule:: {{ fullname }}
 
    {% block attributes %}
-   {% if attributes %}
+   {%- if attributes %}
    .. rubric:: {{ _('Module Attributes') }}
 
    .. autosummary::
@@ -11,10 +11,10 @@
       {{ item }}
    {%- endfor %}
    {% endif %}
-   {% endblock %}
+   {%- endblock %}
 
-   {% block functions %}
-   {% if functions %}
+   {%- block functions %}
+   {%- if functions %}
    .. rubric:: {{ _('Functions') }}
 
    .. autosummary::
@@ -22,10 +22,10 @@
       {{ item }}
    {%- endfor %}
    {% endif %}
-   {% endblock %}
+   {%- endblock %}
 
-   {% block classes %}
-   {% if classes %}
+   {%- block classes %}
+   {%- if classes %}
    .. rubric:: {{ _('Classes') }}
 
    .. autosummary::
@@ -33,10 +33,10 @@
       {{ item }}
    {%- endfor %}
    {% endif %}
-   {% endblock %}
+   {%- endblock %}
 
-   {% block exceptions %}
-   {% if exceptions %}
+   {%- block exceptions %}
+   {%- if exceptions %}
    .. rubric:: {{ _('Exceptions') }}
 
    .. autosummary::
@@ -44,10 +44,10 @@
       {{ item }}
    {%- endfor %}
    {% endif %}
-   {% endblock %}
+   {%- endblock %}
 
-{% block modules %}
-{% if modules %}
+{%- block modules %}
+{%- if modules %}
 .. rubric:: Modules
 
 .. autosummary::
@@ -57,4 +57,4 @@
    {{ item }}
 {%- endfor %}
 {% endif %}
-{% endblock %}
+{%- endblock %}
diff --git a/sphinx/project.py b/sphinx/project.py
index 0ac9f1e94..4a0fa1487 100644
--- a/sphinx/project.py
+++ b/sphinx/project.py
@@ -5,12 +5,13 @@
 import contextlib
 import os
 from glob import glob
+from pathlib import Path
 from typing import TYPE_CHECKING
 
 from sphinx.locale import __
 from sphinx.util import logging
 from sphinx.util.matching import get_matching_files
-from sphinx.util.osutil import path_stabilize, relpath
+from sphinx.util.osutil import os_path, path_stabilize, relpath
 
 if TYPE_CHECKING:
     from collections.abc import Iterable
@@ -24,7 +25,7 @@ class Project:
 
     def __init__(self, srcdir: str | os.PathLike[str], source_suffix: Iterable[str]) -> None:
         #: Source directory.
-        self.srcdir = srcdir
+        self.srcdir = Path(srcdir)
 
         #: source_suffix. Same as :confval:`source_suffix`.
         self.source_suffix = tuple(source_suffix)
@@ -114,6 +115,7 @@ def doc2path(self, docname: str, absolute: bool) -> str:
             # Backwards compatibility: the document does not exist
             filename = docname + self._first_source_suffix
 
+        filename = os_path(filename)
         if absolute:
             return os.path.join(self.srcdir, filename)
         return filename


Test patch:
diff --git a/tests/roots/test-ext-autosummary-module_prefix/conf.py b/tests/roots/test-ext-autosummary-module_prefix/conf.py
new file mode 100644
index 000000000..1065b9112
--- /dev/null
+++ b/tests/roots/test-ext-autosummary-module_prefix/conf.py
@@ -0,0 +1,8 @@
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('.'))
+
+extensions = [
+    'sphinx.ext.autosummary',
+]
diff --git a/tests/roots/test-ext-autosummary-module_prefix/index.rst b/tests/roots/test-ext-autosummary-module_prefix/index.rst
new file mode 100644
index 000000000..fe0b13c43
--- /dev/null
+++ b/tests/roots/test-ext-autosummary-module_prefix/index.rst
@@ -0,0 +1,5 @@
+.. autosummary::
+   :toctree: docs/pkg
+   :recursive:
+
+   pkg
diff --git a/tests/roots/test-ext-autosummary-module_prefix/pkg/__init__.py b/tests/roots/test-ext-autosummary-module_prefix/pkg/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/roots/test-ext-autosummary-module_prefix/pkg/mod0/__init__.py b/tests/roots/test-ext-autosummary-module_prefix/pkg/mod0/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/roots/test-ext-autosummary-module_prefix/pkg/mod1/__init__.py b/tests/roots/test-ext-autosummary-module_prefix/pkg/mod1/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_extensions/test_ext_autosummary.py b/tests/test_extensions/test_ext_autosummary.py
index 1aa8f4afb..e3f034c3b 100644
--- a/tests/test_extensions/test_ext_autosummary.py
+++ b/tests/test_extensions/test_ext_autosummary.py
@@ -506,12 +506,20 @@ def test_autosummary_recursive(app, status, warning):
 
     # Check content of recursively generated stub-files
     content = (app.srcdir / 'generated' / 'package.rst').read_text(encoding='utf8')
-    assert 'package.module' in content
-    assert 'package.package' in content
-    assert 'package.module_importfail' in content
+    assert 'module' in content
+    assert 'package' in content
+    assert 'module_importfail' in content
+    # we no longer generate fully-qualified module names.
+    assert 'package.module' not in content
+    assert 'package.package' not in content
+    assert 'package.module_importfail' not in content
 
     content = (app.srcdir / 'generated' / 'package.package.rst').read_text(encoding='utf8')
-    assert 'package.package.module' in content
+    assert 'module' in content
+    assert 'package.package.module' not in content
+
+    warnings = app.warning.getvalue()
+    assert 'Summarised items should not include the current module.' not in warnings
 
 
 @pytest.mark.sphinx('dummy', testroot='ext-autosummary-recursive',
@@ -599,11 +607,11 @@ def test_autosummary_imported_members(app, status, warning):
         assert ('   .. autosummary::\n'
                 '   \n'
                 '      Bar\n'
-                '   \n' in module)
+                '   ' in module)
         assert ('   .. autosummary::\n'
                 '   \n'
                 '      foo\n'
-                '   \n' in module)
+                '   ' in module)
     finally:
         sys.modules.pop('autosummary_dummy_package', None)
 
@@ -627,7 +635,7 @@ def test_autosummary_module_all(app, status, warning):
         assert ('.. autosummary::\n'
                 '   :toctree:\n'
                 '   :recursive:\n\n'
-                '   autosummary_dummy_package_all.extra_dummy_module\n\n' in module)
+                '   extra_dummy_module\n' in module)
     finally:
         sys.modules.pop('autosummary_dummy_package_all', None)
 
diff --git a/tests/test_extensions/test_ext_autosummary_imports.py b/tests/test_extensions/test_ext_autosummary_imports.py
index 2ac99923f..7420c99f9 100644
--- a/tests/test_extensions/test_ext_autosummary_imports.py
+++ b/tests/test_extensions/test_ext_autosummary_imports.py
@@ -38,3 +38,12 @@ def test_autosummary_import_cycle(app, warning):
         "Replace 'spam.eggs.Ham' with 'Ham'."
     )
     assert expected in app.warning.getvalue()
+
+
+@pytest.mark.sphinx('dummy', testroot='ext-autosummary-module_prefix')
+@pytest.mark.usefixtures("rollback_sysmodules")
+def test_autosummary_generate_prefixes(app, warning):
+    app.build()
+    warnings = app.warning.getvalue()
+    assert 'Summarised items should not include the current module.' not in warnings
+    assert warnings == ''