Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 24 additions & 12 deletions src/pytkdocs/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/test_namespace/subspace/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"The subspace package docstring."
13 changes: 13 additions & 0 deletions tests/test_loader.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down Expand Up @@ -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")
Expand Down