Skip to content

Commit 0c08a14

Browse files
committed
Additional Fix for module spec not loaded correctly (ml-tooling#91)
Attempt at fixing find_module no attribute error from python 3.4+. Although fix does work for python 3.4+, Min python version for lazydoc is still 3.6+ due to fstring usage. (ml-tooling#94) Implement workaround for when relative imports is used in top level modules.
1 parent 5fd9dac commit 0c08a14

File tree

1 file changed

+42
-12
lines changed

1 file changed

+42
-12
lines changed

src/lazydocs/generation.py

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import pkgutil
99
import re
1010
import subprocess
11+
import sys
1112
import types
1213
from dataclasses import dataclass, is_dataclass
1314
from enum import Enum
@@ -1264,25 +1265,41 @@ def generate_docs(
12641265

12651266
for path in paths: # lgtm [py/non-iterable-in-for-loop]
12661267
if os.path.isdir(path):
1268+
path = os.path.abspath(path)
12671269
if validate and subprocess.call(f"{pydocstyle_cmd} {path}", shell=True) > 0:
12681270
raise Exception(f"Validation for {path} failed.")
12691271

12701272
if not stdout_mode:
12711273
print(f"Generating docs for python package at: {path}")
12721274

1275+
# Work around for relative imports in top level modules
1276+
# requires adding parent directory to sys path for parent package resolution
1277+
parent_dir, basename = os.path.split(path)
1278+
if parent_dir not in sys.path:
1279+
sys.path.append(parent_dir)
1280+
12731281
# Generate one file for every discovered module
1274-
for loader, module_name, _ in pkgutil.walk_packages([path]):
1282+
for loader, module_name, _ in pkgutil.walk_packages([path],
1283+
# prefix=f"{basename}."
1284+
):
12751285
if _is_module_ignored(module_name, ignored_modules, private_modules):
12761286
# Add module to ignore list, so submodule will also be ignored
12771287
ignored_modules.append(module_name)
12781288
continue
1289+
full_module_name = f"{basename}.{module_name}"
12791290
try:
12801291
try:
1281-
mod_spec = importlib.util.spec_from_loader(module_name, loader)
1292+
mod_spec = loader.find_spec(module_name)
1293+
# Use full module name as workaround for relative imports
1294+
# in top level modules, not required if using walk_package prefix
1295+
if not mod_spec.parent:
1296+
mod_spec = loader.find_spec(full_module_name)
12821297
mod = importlib.util.module_from_spec(mod_spec)
1298+
if mod.__name__ not in sys.modules:
1299+
sys.modules[mod.__name__] = mod # Add module to current namespace
12831300
mod_spec.loader.exec_module(mod)
12841301
except AttributeError:
1285-
# For older python version compatibility
1302+
# For older python <= 3.4 version compatibility
12861303
mod = loader.find_module(module_name).load_module(module_name) # type: ignore
12871304
module_md = generator.module2md(mod, is_mdx=is_mdx, include_toc=include_toc)
12881305
if not module_md:
@@ -1296,7 +1313,7 @@ def generate_docs(
12961313
else:
12971314
to_md_file(
12981315
module_md,
1299-
mod.__name__,
1316+
module_name, # Alt to mod.__name__ to be top level package agnostic
13001317
out_path=output_path,
13011318
watermark=watermark,
13021319
is_mdx=is_mdx,
@@ -1306,21 +1323,32 @@ def generate_docs(
13061323
f"Failed to generate docs for module {module_name}: " + repr(ex)
13071324
)
13081325
elif os.path.isfile(path):
1326+
path = os.path.abspath(path)
13091327
if validate and subprocess.call(f"{pydocstyle_cmd} {path}", shell=True) > 0:
13101328
raise Exception(f"Validation for {path} failed.")
13111329

13121330
if not stdout_mode:
13131331
print(f"Generating docs for python module at: {path}")
13141332

1315-
module_name = os.path.basename(path)
1333+
# Work around for relative imports usage in top level modules
1334+
# requires adding parent directory to sys path for parent package resolution
1335+
src_dir, filename = os.path.split(path)
1336+
parent_dir = os.path.dirname(src_dir)
1337+
if parent_dir not in sys.path:
1338+
sys.path.append(parent_dir)
1339+
1340+
module_name, _ = os.path.splitext(filename)
1341+
full_module_name = f"{os.path.basename(src_dir)}.{module_name}"
13161342

1317-
spec = importlib.util.spec_from_file_location(
1318-
module_name,
1343+
mod_spec = importlib.util.spec_from_file_location(
1344+
full_module_name,
13191345
path,
13201346
)
1321-
assert spec is not None
1322-
mod = importlib.util.module_from_spec(spec)
1323-
spec.loader.exec_module(mod) # type: ignore
1347+
assert mod_spec is not None
1348+
mod = importlib.util.module_from_spec(mod_spec)
1349+
if mod.__name__ not in sys.modules:
1350+
sys.modules[mod.__name__] = mod # Add module to current namespace
1351+
mod_spec.loader.exec_module(mod) # type: ignore
13241352

13251353
if mod:
13261354
module_md = generator.module2md(mod, is_mdx=is_mdx, include_toc=include_toc)
@@ -1362,11 +1390,13 @@ def generate_docs(
13621390

13631391
try:
13641392
try:
1365-
mod_spec = importlib.util.spec_from_loader(module_name, loader)
1393+
mod_spec = loader.find_spec(module_name)
13661394
mod = importlib.util.module_from_spec(mod_spec)
1395+
if mod.__name__ not in sys.modules:
1396+
sys.modules[mod.__name__] = mod # Add module to current namespace
13671397
mod_spec.loader.exec_module(mod)
13681398
except AttributeError:
1369-
# For older python version compatibility
1399+
# For older python <= 3.4 version compatibility
13701400
mod = loader.find_module(module_name).load_module(module_name) # type: ignore
13711401
module_md = generator.module2md(mod, is_mdx=is_mdx, include_toc=include_toc)
13721402

0 commit comments

Comments
 (0)