Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Viewcode: Fix issue with import paths that differ from the directory structure #13195

Merged
merged 38 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8bcf410
Improve module path finder
ProGamerGov Dec 26, 2024
b332cf7
Add tests
ProGamerGov Dec 26, 2024
f6cf7df
add test
ProGamerGov Dec 26, 2024
b926d82
Fix try except issue
ProGamerGov Dec 26, 2024
67c732d
Fix linting errors
ProGamerGov Dec 26, 2024
bf614af
Fix lint
ProGamerGov Dec 26, 2024
bcaec12
remove old NOQA
ProGamerGov Dec 26, 2024
487d4ae
Remove NOQA
ProGamerGov Dec 26, 2024
4f2e401
change test name
ProGamerGov Dec 26, 2024
2b998c3
fix test lint
ProGamerGov Dec 26, 2024
0ae96b1
fix lint quotation marks
ProGamerGov Dec 26, 2024
6e3cfcb
Update authors & changes files
ProGamerGov Dec 26, 2024
dafd181
Merge branch 'master' into master-attempt-2
ProGamerGov Jan 4, 2025
dc4ec10
Fix lint errors
ProGamerGov Jan 5, 2025
761d253
Merge branch 'master' into master-attempt-2
ProGamerGov Jan 5, 2025
8c98620
Update viewcode.py
ProGamerGov Jan 5, 2025
fd52bc1
Update viewcode.py
ProGamerGov Jan 5, 2025
abbadbb
Fix remaining lint issues
ProGamerGov Jan 5, 2025
e91eb2e
fix mistake
ProGamerGov Jan 5, 2025
768145c
Merge branch 'master' into master-attempt-2
ProGamerGov Jan 5, 2025
bcbccd1
fix remaining lint errors
ProGamerGov Jan 5, 2025
49ff6f0
Merge branch 'master-attempt-2' of /~https://github.com/ProGamerGov/sph…
ProGamerGov Jan 5, 2025
965cc26
fix classes
ProGamerGov Jan 5, 2025
6fa7a43
replace __import__ with find_spec
ProGamerGov Jan 5, 2025
adca233
Merge branch 'master' into master-attempt-2
ProGamerGov Jan 5, 2025
87f01b1
Fix lint errors
ProGamerGov Jan 5, 2025
214ab7b
Merge branch 'master' into master-attempt-2
AA-Turner Jan 5, 2025
a3ff8cb
format
AA-Turner Jan 5, 2025
e06b219
early return
AA-Turner Jan 5, 2025
a7d3d1c
deduplicate
AA-Turner Jan 5, 2025
7e5dc56
Use non-deprecated exec_module
AA-Turner Jan 5, 2025
6849c71
Use module.__name__ instead of parsing the module repr
AA-Turner Jan 5, 2025
eada6e7
module is import_module(module.__name__)
AA-Turner Jan 5, 2025
60f7529
style
AA-Turner Jan 5, 2025
6e435dd
absolute imports
AA-Turner Jan 5, 2025
9dfde4b
unify with the 'import_module' approach
AA-Turner Jan 5, 2025
6ba6460
Credit
AA-Turner Jan 5, 2025
69766d1
remove unused import
AA-Turner Jan 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Contributors
* Antonio Valentino -- qthelp builder, docstring inheritance
* Antti Kaihola -- doctest extension (skipif option)
* Barry Warsaw -- setup command improvements
* Ben Egan -- Napoleon improvements
* Ben Egan -- Napoleon improvements & viewcode improvements
* Benjamin Peterson -- unittests
* Blaise Laflamme -- pyramid theme
* Brecht Machiels -- builder entry-points
Expand Down
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Bugs fixed
term indices before accessing them.
* #11233: linkcheck: match redirect URIs against :confval:`linkcheck_ignore` by
overriding session-level ``requests.get_redirect_target``.
* #13195: viewcode: Fix issue where import paths differ from the directory
structure.
Patch by Ben Egan and Adam Turner.

Testing
-------
28 changes: 23 additions & 5 deletions sphinx/ext/viewcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

from __future__ import annotations

import importlib.util
import operator
import os.path
import posixpath
import traceback
from importlib import import_module
from typing import TYPE_CHECKING, Any, cast

from docutils import nodes
Expand Down Expand Up @@ -48,12 +48,30 @@ class viewcode_anchor(Element):


def _get_full_modname(modname: str, attribute: str) -> str | None:
if modname is None:
# Prevents a TypeError: if the last getattr() call will return None
# then it's better to return it directly
return None

try:
if modname is None:
# Prevents a TypeError: if the last getattr() call will return None
# then it's better to return it directly
# Attempt to find full path of module
module_path = modname.split('.')
num_parts = len(module_path)
for i in range(num_parts, 0, -1):
mod_root = '.'.join(module_path[:i])
module_spec = importlib.util.find_spec(mod_root)
if module_spec is not None:
break
else:
return None
# Load and execute the module
module = importlib.util.module_from_spec(module_spec)
if module_spec.loader is None:
return None
module = import_module(modname)
module_spec.loader.exec_module(module)
if i != num_parts:
for mod in module_path[i:]:
module = getattr(module, mod)

# Allow an attribute to have multiple parts and incidentally allow
# repeated .s in the attribute.
Expand Down
24 changes: 24 additions & 0 deletions tests/roots/test-ext-viewcode-find-package/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import os
import sys

source_dir = os.path.abspath('.')
if source_dir not in sys.path:
sys.path.insert(0, source_dir)
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
exclude_patterns = ['_build']


if 'test_linkcode' in tags: # NoQA: F821 (tags is injected into conf.py)
extensions.remove('sphinx.ext.viewcode')
extensions.append('sphinx.ext.linkcode')

def linkcode_resolve(domain, info):
if domain == 'py':
fn = info['module'].replace('.', '/')
return 'http://foobar/source/%s.py' % fn
elif domain == 'js':
return 'http://foobar/js/' + info['fullname']
elif domain in {'c', 'cpp'}:
return 'http://foobar/%s/%s' % (domain, ''.join(info['names']))
else:
raise AssertionError
10 changes: 10 additions & 0 deletions tests/roots/test-ext-viewcode-find-package/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
viewcode
========

.. currentmodule:: main_package.subpackage.submodule

.. autofunction:: func1

.. autoclass:: Class1

.. autoclass:: Class3
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from main_package import subpackage
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from main_package.subpackage._subpackage2 import submodule

__all__ = ['submodule']
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
submodule
"""
# raise RuntimeError('This module should not get imported')


def decorator(f):
return f


@decorator
def func1(a, b):
"""
this is func1
"""
return a, b


@decorator
class Class1:
"""
this is Class1
"""


class Class3:
"""
this is Class3
"""

class_attr = 42
"""this is the class attribute class_attr"""
21 changes: 21 additions & 0 deletions tests/test_extensions/test_ext_viewcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,24 @@ def find_source(app, modname):
'This is the class attribute class_attr',
):
assert result.count(needle) == 1


@pytest.mark.sphinx('html', testroot='ext-viewcode-find-package', freshenv=True)
def test_find_local_package_import_path(app, status, warning):
app.builder.build_all()
result = (app.outdir / 'index.html').read_text(encoding='utf8')

count_func1 = result.count(
'href="_modules/main_package/subpackage/_subpackage2/submodule.html#func1"'
)
assert count_func1 == 1

count_class1 = result.count(
'href="_modules/main_package/subpackage/_subpackage2/submodule.html#Class1"'
)
assert count_class1 == 1

count_class3 = result.count(
'href="_modules/main_package/subpackage/_subpackage2/submodule.html#Class3"'
)
assert count_class3 == 1
Loading