Source code for exosim.utils.klass_factory
import inspect
import pkgutil
from importlib import import_module
_KLASS_CACHE = {}
[docs]
def find_klass_in_file(python_file, baseclass):
"""
It finds in the indicated python file a class that is a subclass of the given one.
Parameters
----------
python_file: str
python file name
baseclass: class
reference class to search for
Returns
-------
class:
class found in the python file.
"""
import importlib.util
spec = importlib.util.spec_from_file_location("foo", python_file)
foo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(foo)
classes = [
m[1]
for m in inspect.getmembers(foo, inspect.isclass)
if m[1] is not baseclass and issubclass(m[1], baseclass)
]
if len(classes) == 0:
raise ImportError(f"No class inheriting from {baseclass} in {python_file}")
return classes[0]
[docs]
def load_klass(input, baseclass):
"""
It returns a class that is a subclass of the given base class.
Parameters
----------
input: str or class
if is a string, :func:`find_klass_in_file` is used to return the right class.
If is a class, it checks whether it is an eligible class or not.
baseclass: class
reference class to search for
Returns
-------
class:
subclass of baseclass
"""
if isinstance(input, str):
return find_klass_in_file(input, baseclass)
raise TypeError("task model in the wrong format")
def _find_class_in_module_recursive(module_path, class_name, baseclass, parent_path=""):
"""
Recursively search for a class in a module and its submodules.
Parameters
----------
module_path: str
The module path to search in
class_name: str
The name of the class to find
baseclass: class
The base class that the target class should inherit from
parent_path: str
The parent module path (used internally for recursion)
Returns
-------
class or None
The found class or None if not found
"""
try:
full_path = f"{parent_path}.{module_path}" if parent_path else module_path
module = import_module(full_path)
# Check if class exists in current module
if hasattr(module, class_name):
klass = getattr(module, class_name)
if inspect.isclass(klass) and issubclass(klass, baseclass):
return klass
# Recursively search in submodules
if hasattr(module, "__path__"):
for module_info in pkgutil.iter_modules(module.__path__):
result = _find_class_in_module_recursive(
module_info.name, class_name, baseclass, full_path
)
if result is not None:
return result
except (ModuleNotFoundError, AttributeError, TypeError, ImportError):
pass
return None
[docs]
def find_task(input, baseclass, module_path="exosim.tasks"):
"""
It looks for a class that is a subclass of the base class indicated.
Parameters
----------
input: str or object
can either be a string indicating a class name, a python file, or it can be a class.
baseclass: object
reference class
Returns
-------
object
"""
if isinstance(input, str):
if input == baseclass.__name__:
# import the base class
klass = baseclass
elif input.endswith(".py"):
# import from file
klass = load_klass(input, baseclass)
else:
# Check cache first
cache_key = (module_path, input)
if cache_key in _KLASS_CACHE:
return _KLASS_CACHE[cache_key]
# Search recursively for task class by name
klass = _find_class_in_module_recursive(module_path, input, baseclass)
if klass is not None:
_KLASS_CACHE[cache_key] = klass
else:
raise TypeError(f"Class '{input}' not found in {module_path} modules")
elif issubclass(input, baseclass):
klass = input
else:
raise TypeError
return klass
[docs]
def find_and_run_task(parameters, key, baseclass):
"""
It looks in the input parameters for a class that is a subclass of the base class indicated, and it initialises it.
Parameters
----------
parameters: dict
input dictionaty
key: str
string indicating the keyword for the class name
baseclass: object
reference class
Returns
-------
callable
"""
try:
task = find_task(parameters[key], baseclass) if key in parameters else baseclass
except UnboundLocalError as exc:
raise Exception(f"unable to find and instantiate a {baseclass} class") from exc
return task()