Source code for pyspedas.utilities.libs

"""
Displays location of source files, one line of documentation and the function name based on the request
"""

import importlib
import inspect
import itertools
import pkgutil
import sys
import io
from fnmatch import fnmatchcase

import pyspedas
import functools


[docs] def libs(function_name, package=None): """ Search for a specified function by exact name, substring, or wildcard within a given package and its submodules. Parameters ---------- function_name (str): The name or partial name of the function to search for. If "*" or "?" are found in the function name, a case-insensitive wildcard match is performed, otherwise function_name is treated as a substring to match. package (module, optional): The Python package in which to search for the function. Default is the pyspedas package. This should be a Python module object. Note ---- All submodules of pyspedas are imported during the search. The package option is simply narrows the search. The function specifically searches for functions, not classes or other objects. If multiple functions with the same name exist in different modules within the package, it will list them all. The function handles ImportError exceptions by printing an error message and continuing the search. Example ------- >>> from pyspedas import libs >>> libs('fgm') >>> libs('fgm', package=pyspedas.projects.mms) """ # Gate for no function_name if not function_name: return # Using separate functions for the wildcard and substring matching eliminates a test and branch in the inner loop def list_functions_substring(module, root, search_string, pacakge_obj): full_module_name = module.__name__ for name, obj in inspect.getmembers(module): if inspect.isfunction(obj) and (search_string in name) and (pacakge_obj.__name__ in obj.__module__): full_name = full_module_name + '.' + name source_file = inspect.getsourcefile(obj) doc = inspect.getdoc(obj) first_line_of_doc = doc.split('\n')[0] if doc else "No documentation" print(f"Function: {full_name}\nLocation: {source_file}\nDocumentation: {first_line_of_doc}\n") elif isinstance(obj, functools.partial) and (search_string in name): original_func = obj.func full_name = full_module_name + '.' + name source_file = inspect.getsourcefile(original_func) doc = inspect.getdoc(original_func) first_line_of_doc = doc.split('\n')[0] if doc else "No documentation" print(f"Partial Function: {full_name}\nLocation: {source_file}\nDocumentation: {first_line_of_doc}\n") def list_functions_wildcard(module, root, wildcard_pattern, pacakge_obj): full_module_name = module.__name__ for name, obj in inspect.getmembers(module): if inspect.isfunction(obj) and fnmatchcase(name.lower(), wildcard_pattern) and (pacakge_obj.__name__ in obj.__module__): full_name = full_module_name + '.' + name source_file = inspect.getsourcefile(obj) doc = inspect.getdoc(obj) first_line_of_doc = doc.split('\n')[0] if doc else "No documentation" print(f"Function: {full_name}\nLocation: {source_file}\nDocumentation: {first_line_of_doc}\n") elif isinstance(obj, functools.partial) and fnmatchcase(name.lower(), wildcard_pattern): original_func = obj.func full_name = full_module_name + '.' + name source_file = inspect.getsourcefile(original_func) doc = inspect.getdoc(original_func) first_line_of_doc = doc.split('\n')[0] if doc else "No documentation" print(f"Partial Function: {full_name}\nLocation: {source_file}\nDocumentation: {first_line_of_doc}\n") def traverse_modules(package, function_name, package_obj): # Add the module itself walk_packages_iterator = pkgutil.walk_packages(path=package.__path__, prefix=package.__name__ + '.') combined_iterator = itertools.chain(((None, package.__name__, True),), walk_packages_iterator) # Check for wildcard characters if '*' in function_name or '?' in function_name: wildcard=True # There is no 'fnmatchnocase', so lowercase the search pattern and function names before comparing # We'll add implicit leading and trailing '*', so any substring match will appear in the list wildcard_pattern = '*' + function_name.lower() + '*' else: wildcard=False wildcard_pattern='' for _, modname, ispkg in combined_iterator: if ispkg and 'qtplotter' not in modname.lower(): # Save the current stdout so that we can restore it later original_stdout = sys.stdout # Redirect stdout to a dummy stream sys.stdout = io.StringIO() try: module = importlib.import_module(modname) if not package_obj: package_obj = package # Restore the original stdout sys.stdout = original_stdout if not wildcard: list_functions_substring(module, package, function_name, package_obj) else: list_functions_wildcard(module, package, wildcard_pattern, package_obj) except ImportError as e: # Restore the original stdout sys.stdout = original_stdout print(f"Error importing module {modname}: {e}") finally: # Restore the original stdout sys.stdout = original_stdout for module in [pyspedas]: if not package or module.__name__ in package.__name__: traverse_modules(module, function_name, package)