Source code for quantify_scheduler.helpers.inspect

# Repository: https://gitlab.com/quantify-os/quantify-scheduler
# Licensed according to the LICENCE file on the main branch
"""Python inspect helper functions."""
from __future__ import annotations

import inspect
import os
import subprocess as sp  # nosec B404
import sys
from pathlib import Path
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from types import ModuleType


[docs] def get_classes(*modules: ModuleType) -> dict[str, type[Any]]: """ Return a dictionary of class names by class types. .. code-block:: from quantify_scheduler.helpers import inspect from my_module import foo class_dict: dict[str, type] = inspect.get_classes(foo) print(class_dict) // { 'Bar': my_module.foo.Bar } Parameters ---------- modules : Variable length of modules. Returns ------- : A dictionary containing the class names by class reference. """ classes = list() for module in modules: module_name: str = module.__name__ classes += inspect.getmembers( sys.modules[module_name], lambda member: inspect.isclass(member) and member.__module__ == module_name, ) return dict(classes)
[docs] def make_uml_diagram( obj_to_plot: ModuleType | type[Any], options: list[str], ) -> str | None: """ Generate a UML diagram of a given module or class. This function is a wrapper of `pylint.pyreverse`. Parameters ---------- obj_to_plot The module or class to visualize options A string containing the plotting options for pyreverse Returns ------- : The name of the generated ``png`` image """ basic_options = ["--colorized", "-m", "n"] sp_err = ( f"Something went wrong in the plotting backend. " f"Please make sure pylint is installed and the provided options have the " f"correct syntax: {options}" ) dot_err = "Error running 'dot': is 'graphviz' installed?" if inspect.ismodule(obj_to_plot): assert obj_to_plot.__file__ is not None abs_module_path = Path(obj_to_plot.__file__).parent try: sp.run( # nosec B603 [ "pyreverse", "--only-classnames", *basic_options, *options, abs_module_path, ], check=True, stdout=sp.DEVNULL, stderr=sp.STDOUT, ) except (sp.CalledProcessError, FileNotFoundError): # FileNotFoundError is raised, as opposed to CalledProcessError, # when the executable is not found. print(sp_err) try: diagram_name = f"{abs_module_path.name}.png" sp.run(["dot", "-Tpng", "classes.dot", "-o", diagram_name], check=True) # nosec B603 os.remove("classes.dot") os.remove("packages.dot") except (sp.CalledProcessError, FileNotFoundError): print(dot_err) diagram_name = None elif inspect.isclass(obj_to_plot): class_module_str = obj_to_plot.__module__ class_name_str = obj_to_plot.__name__ class_path_str = f"{class_module_str}.{class_name_str}" class_module = sys.modules[class_module_str] assert class_module.__file__ is not None repo_path = str(Path(class_module.__file__).parent) try: sp.run( # nosec B603 [ "pyreverse", *basic_options, *options, "-c", class_path_str, repo_path, ], check=True, stdout=sp.DEVNULL, stderr=sp.STDOUT, ) except (sp.CalledProcessError, FileNotFoundError): print(sp_err) try: diagram_name = f"{class_name_str}.png" sp.run( # nosec B603 ["dot", "-Tpng", f"{class_path_str}.dot", "-o", diagram_name], check=True, ) os.remove(f"{class_path_str}.dot") except (sp.CalledProcessError, FileNotFoundError): print(dot_err) diagram_name = None else: raise TypeError("Argument must be either a module or a class") return diagram_name