Skip to content

Commit

Permalink
ENH: Adding function to help generate __init__.pyi
Browse files Browse the repository at this point in the history
An interface file can be used to provide introspection
into a libary.

```
python3 ${ITK_BUILD_DIR}/Wrapping/Generators/Python/itk_generate_pyi.py
```

The .pyi file provides hints to IDE's so that
they can help with dynamic introspection
during code development.

The itk/__init__.py file 'lifts symbols' from
the individial modules into the itk package namespace
in a dynamic way that is not visible by many IDE's,
the explicit generation of an interface file can
provide the necessary information needed to assist
development.
  • Loading branch information
hjmjohnson committed Nov 23, 2020
1 parent f6c98a5 commit 82754b5
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 3 deletions.
5 changes: 2 additions & 3 deletions Wrapping/Generators/Python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,9 @@ if(NOT EXTERNAL_WRAP_ITK_PROJECT)
itk/support/itkHelpers
itk/support/itkInitHelpers
itk/__init__
itk/itkInitHelpers
itk_generate_pyi # A utility function used to generate the intreface file
)
# Done listing files.

# Now copy these files if necessary.

set(ITK_WRAP_PYTHON_INSTALL_FILES )
Expand Down Expand Up @@ -1000,5 +999,5 @@ add_custom_command(OUTPUT ${ITK_WRAP_PYTHON_ROOT_BINARY_DIR}/itk/__init__.pyi
COMMAND ${Python3_EXECUTABLE} ${ITK_WRAP_PYTHON_ROOT_BINARY_DIR}/itk_generate_pyi.py
DEPENDS ${ITK_WRAP_PYTHON_FILES} ${WRAPPER_LIBRARY_NAME}Swig
WORKING_DIRECTORY ${ITK_WRAP_PYTHON_ROOT_BINARY_DIR}
COMMENT "Building pythong type interface file itk/__init__.pyi for the itk package"
COMMENT "Building python type interface file itk/__init__.pyi for the itk package"
)
85 changes: 85 additions & 0 deletions Wrapping/Generators/Python/itk_generate_pyi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from typing import List, Any
import inspect
import importlib
import sys

try:
# First attempt using convention of build directory
from pathlib import Path

wrap_itk_pth: Path = Path(__file__).parent / "WrapITK.pth"

if not wrap_itk_pth.is_file():
print(
"ERROR: itk_generate_pyi.py must be run in the same directory as the WrapITK.pth file"
)

with open(wrap_itk_pth, "r") as fid:
itk_module_paths = [
itk_module_path.strip() for itk_module_path in fid.readlines()
]
for pp in itk_module_paths:
if not pp.startswith("#"):
sys.path.append(pp)
import itkConfig
except:
# Second attempt on the standard path
import itkConfig

itkConfig.LazyLoading = False
itkConfig.DumpInterfaces = True

requested_module_name = "itk"
requested_module = importlib.import_module(requested_module_name)

# Can not dump complete .pyi interface file if LazyLoading is ued
class ITKSignaturesList:
"""
A pure static class to manage dumping a .pyi file for
the itk_module.
"""

_itk_namespace_list: List[str] = []
_broken_introspection_signatures: List[str] = ["echo", "image", "string", "str"]

@staticmethod
def parse_object(obj_name: str, obj: Any):
# builtin classes do not have introspection signatures.
if inspect.isbuiltin(obj):
return
elif obj_name in ITKSignaturesList._broken_introspection_signatures:
return
elif obj_name.startswith("_"):
return
elif inspect.isclass(obj):
ITKSignaturesList._itk_namespace_list.append(f"class {obj_name}:")
methods_exists: bool = False
for elem_name, elem_obj in obj.__dict__.items():
if inspect.ismethod(elem_obj) or inspect.isfunction(elem_obj):
methods_exists = True
ITKSignaturesList._itk_namespace_list.append(
f" def {elem_name}{inspect.signature(elem_obj)}: ..."
)
if not methods_exists:
ITKSignaturesList._itk_namespace_list[-1] += " ..."
elif inspect.isfunction(obj):
ITKSignaturesList._itk_namespace_list.append(
f"def {obj_name}{inspect.signature(obj)}: ..."
)
# else:
# print(f"{obj_name}: {type(obj)}")

@staticmethod
def dumps(dump_file: str) -> None:
with open(dump_file, "w") as fid:
for ln in ITKSignaturesList._itk_namespace_list:
fid.write(f"{ln}\n")


# Now iterate through all module items and print of signatures
# of the objects collected. The generation of a .pyi file
# allows IDE's and other tools to do better introspection
all_items = list(requested_module.__dict__.items())
for k, v in all_items:
ITKSignaturesList.parse_object(k, v)
ITKSignaturesList.dumps(requested_module.__file__ + "i")

0 comments on commit 82754b5

Please sign in to comment.