diff --git a/src/pytkdocs/objects.py b/src/pytkdocs/objects.py index 751e675..6ec23d0 100644 --- a/src/pytkdocs/objects.py +++ b/src/pytkdocs/objects.py @@ -173,20 +173,32 @@ def relative_file_path(self) -> str: If the relative file path cannot be determined, the value returned is `""` (empty string). """ - top_package_name = self.path.split(".", 1)[0] - try: - top_package = sys.modules[top_package_name] - except KeyError: + + parts = self.path.split(".") + namespaces = [".".join(parts[:l]) for l in range(1, len(parts) + 1)] + # Iterate through all sub namespaces including the last in case it is a module + for namespace in namespaces: + try: + importlib.import_module(namespace) + top_package = sys.modules[namespace] + except (ImportError, ModuleNotFoundError, KeyError): + # ImportError: Triggered if the namespace is not importable + # ModuleNotFoundError: Triggered if the namespace is not a module + # KeyError: Triggered if the imported package isn't referenced under the same fully qualified name + # Namespace packages are importable, so this should work for them + return "" + try: - importlib.import_module(top_package_name) - except ImportError: + top_package_path = Path(inspect.getabsfile(top_package)).parent + return str(Path(self.file_path).relative_to(top_package_path.parent)) + except TypeError: + # Triggered if getabsfile() can't be found in the case of a Namespace package + pass + except ValueError: + # Triggered if Path().relative_to can't find an appropriate path return "" - top_package = sys.modules[top_package_name] - top_package_path = Path(inspect.getabsfile(top_package)).parent - try: - return str(Path(self.file_path).relative_to(top_package_path.parent)) - except ValueError: - return "" + + return "" @property def name_to_check(self) -> str: diff --git a/tests/fixtures/test_namespace/subspace/__init__.py b/tests/fixtures/test_namespace/subspace/__init__.py new file mode 100644 index 0000000..3097a32 --- /dev/null +++ b/tests/fixtures/test_namespace/subspace/__init__.py @@ -0,0 +1 @@ +"The subspace package docstring." diff --git a/tests/test_loader.py b/tests/test_loader.py index a2d4fda..2dfa22a 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,11 +1,14 @@ """Tests for [the `loader` module][pytkdocs.loader].""" import sys +from pathlib import Path import pytest from pytkdocs.loader import Loader, get_object_tree +from . import FIXTURES_DIR + def test_import_no_path(): with pytest.raises(ValueError): @@ -82,6 +85,16 @@ def test_loading_package(): assert obj.docstring == "The package docstring." +def test_loading_namespace_package(): + loader = Loader() + old_paths = list(sys.path) + sys.path.append(str(Path(FIXTURES_DIR).resolve())) + obj = loader.get_object_documentation("test_namespace.subspace") + assert obj.docstring == "The subspace package docstring." + assert obj.relative_file_path == "subspace/__init__.py" + sys.path = old_paths + + def test_loading_module(): loader = Loader() obj = loader.get_object_documentation("tests.fixtures.the_package.the_module")