Skip to content

Commit

Permalink
fix: Fix relative path for native namespace packages
Browse files Browse the repository at this point in the history
Native namespace packages don't have an `__init__.py` module.
This commit adds support for finding relative file path
of such packages.

References: #19, #22.
  • Loading branch information
shyamd authored and pawamoy committed Apr 25, 2020
1 parent ab1efe5 commit a74dccf
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 12 deletions.
36 changes: 24 additions & 12 deletions src/pytkdocs/objects.py
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
@@ -0,0 +1 @@
"The subspace package docstring."
13 changes: 13 additions & 0 deletions 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):
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

0 comments on commit a74dccf

Please sign in to comment.