Source code for laborchestrator.pythonlab_process_finder
"""
This module provides the ProcessFinder.
A tool ta get a list of all available PythonLan processes in a specified directory or module.
"""
import importlib
import inspect
import pkgutil
from contextlib import suppress
from types import ModuleType
from typing import NamedTuple, List
from pythonlab.process import PLProcess
[docs]
class ImportableProcess(NamedTuple):
module: ModuleType
name: str
[docs]
class ProcessFinder:
[docs]
@staticmethod
def get_processes(pck: ModuleType) -> List[ImportableProcess]:
process_names = ProcessFinder._find_processes(pck)
return process_names
[docs]
@staticmethod
def _find_processes(pck: ModuleType) -> List[ImportableProcess]:
"""
Iterate through the given process package and find PLProcess classes.
Specifying the second argument (prefix) to iter_modules makes the
returned name an absolute name instead of a relative one. This allows
import_module to work without having to do additional modification to
the name.
s. https://packaging.python.org/guides/creating-and-discovering-plugins/
"""
processes = []
for _finder, name, ispkg in pkgutil.iter_modules(pck.__path__):
if ispkg:
continue
mod_name = pck.__name__ + "." + name
try:
submodule = importlib.import_module(mod_name)
except Exception:
continue
for attr, obj in vars(submodule).items():
with suppress(Exception):
if ProcessFinder._is_importable_process_type(obj, submodule):
processes.append(ImportableProcess(submodule, attr))
return processes
[docs]
@staticmethod
def _is_importable_process_type(obj: object, module: ModuleType) -> bool:
if not inspect.isclass(obj):
return False
if obj is PLProcess:
return False
if inspect.isabstract(obj):
return False
if obj.__module__ != module.__name__:
return False
if not issubclass(obj, PLProcess):
return False
return ProcessFinder._can_be_constructed_without_args(obj)
[docs]
@staticmethod
def _can_be_constructed_without_args(process_type: type[PLProcess]) -> bool:
try:
signature = inspect.signature(process_type)
except (TypeError, ValueError):
return False
for parameter in signature.parameters.values():
if parameter.kind in (
inspect.Parameter.VAR_POSITIONAL,
inspect.Parameter.VAR_KEYWORD,
):
continue
if parameter.default is inspect.Parameter.empty:
return False
return True
[docs]
@staticmethod
def create_process(importable_process: ImportableProcess) -> PLProcess:
process = getattr(importable_process.module, importable_process.name)()
assert isinstance(process, PLProcess)
return process