# meta.py
#
# Add meta features to functions of a module
#
# Copyright (c) 2025 CWordTM Project
# Author: Johnny Cheng <drjohnnycheng@gmail.com>
#
# Updated: 15-Jun-2024 (0.6.4), 15-Jan-2025, 2-Feb-2025 (0.7.7)
#
# URL: https://github.com/drjohnnycheng/cwordtm.git
# For license information, see LICENSE.TXT
import ast
import inspect
from functools import wraps
import pkgutil
from importlib import import_module
import types
import time
[docs]
def get_module_info(detailed=False):
"""Gets the information of the module 'cwordtm'.
:param detailed: The flag indicating whether only function signature or
detailed source code is shown, default to False
:type detailed: bool, optional
:return: The information of the module 'cwordtm'
:rtype: str
"""
mod_name = __name__.split('.')[0] # Current module
module = import_module(mod_name)
func1 = "@validate_params"
func2 = "def cosine_similarity"
i = 0
mod_info = "The member information of the module '" + mod_name + "'\n"
for _, submodname, ispkg in pkgutil.iter_modules(module.__path__):
if not ispkg:
i += 1
mod_info += "%d. Submodule %s:" %(i, submodname) + "\n"
submod = import_module(".."+submodname, mod_name+"."+submodname)
for name, member in inspect.getmembers(submod):
if (inspect.isclass(member) and name in ['LDA', 'NMF', 'BTM']) or \
(inspect.isfunction(member) and name != 'files' and
not name in func1 and not name in func2):
if detailed:
mod_info += "{}\n".format(inspect.getsource(member))
else:
mod_info += " {} {}\n".format(name, inspect.signature(member))
return mod_info
[docs]
def get_submodule_info(submodname, detailed=False):
"""Gets the information of the prescribed submodule of the module 'cwordtm'.
:param submodname: The name of the prescribed submodule, default to None
:type submodname: str
:param detailed: The flag indicating whether only function signature or
detailed source code is shown, default to False
:type detailed: bool, optional
:return: The information of the prescribed submodule
:rtype: str
"""
mod_name = __name__.split('.')[0] # Current module
module = import_module(mod_name)
mod_info = ""
for _, submod, ispkg in pkgutil.iter_modules(module.__path__):
if not ispkg and submod == submodname:
submod_obj = import_module(".."+submodname, mod_name+"."+submodname)
for name, member in inspect.getmembers(submod_obj):
if (inspect.isclass(member) and name in ['LDA', 'NMF', 'BTM']) or \
(inspect.isfunction(member) and name != 'files'):
if detailed:
mod_info += "{}\n".format(inspect.getsource(member))
else:
mod_info += " {} {}\n".format(name, inspect.signature(member))
if len(mod_info) == 0:
mod_info = "The submodule '" + submodname + "' cannot be found!"
else:
mod_info = "The function(s) of the submodule '" + mod_name + "." + submodname + "':\n\n" + mod_info
return mod_info
[docs]
def get_function(mod_name, submodules, func_name):
"""Gets the object of the function 'func_name' if it belongs
to one of 'submodules' of the current top-level module.
:param mod_name: The name of the source top-level module, default to None
:type mod_name: str
:param submodules: The list of names of the sub-modules of the top-level module
:type submodules: list
:param func_name: The name of the function to be looked for
:type func_name: str
:return: The object of the target function, if any, otherwise None
:rtype: function
"""
for submod in submodules:
mod_obj = import_module(mod_name + "." + submod)
if hasattr(mod_obj, func_name):
return getattr(mod_obj, func_name), submod
return None, None
[docs]
def addin(func):
"""Adds additional features (showing timing information and source code)
to a function at runtime. This adds two parameters ('timing' & 'code') to
function 'func' at runtime. 'timing' is a flag indicating whether
the execution time of the function is shown, and it is default to False.
'code' is an indicator determining if the source code of the function
'func' is shown and/or the function is invoked; '0' indicates the function
is executed but its source code is not shown, '1' indicates the source code
of the function is shown after execution, or '2' indicates the source code
of the function is shown without execution, and it is default to 0.
:param func: The target function for inserting additiolnal features -
timing information and showing code, default to None
:type func: function
:return: The wrapper function
:rtype: function
"""
try:
if "code" in inspect.signature(func).parameters:
return
except ValueError:
return
mod_name = __name__.split('.')[0]
module = import_module(mod_name)
# Get Submodules of cwordtm
submodules = [name for name in dir(module)
if isinstance(getattr(module, name), type(module))]
exclusion = ["files", "WordCloud", "ngrams"]
def next_level(func):
source_code = inspect.getsource(func)
tree = ast.parse(source_code)
for node in ast.walk(tree):
if isinstance(node, ast.Call):
func_call_code = ast.get_source_segment(source_code, node)
func_name = func_call_code.split('(')[0].split(".")[-1]
func_obj, submod = get_function(mod_name, submodules, func_name)
if func_obj is not None and func_name not in exclusion:
module_name = inspect.getmodule(func_obj).__name__
print(">>", module_name + "." + func_name)
try:
print(inspect.getsource(func_obj))
except TypeError:
print(" ** Source code is not available!")
@wraps(func)
def wrapper(*args, timing=False, code=0, **kwargs):
"""Wrapper function to add two parameters ('timing' & 'code') to
function 'func' at runtime.
:param timing: The flag indicating whether the execution time of the
function 'func' is shown, default to False
:type timing: bool, optional
:param code: The indicator determining if the source code of the function
'func' is shown and/or the function is invoked; '0' indicates the function
is executed but its source code is not shown, '1' indicates the source code
of the function is shown after execution, or '2' indicates the source code
of the function is shown without execution, default to 0
:type code: bool, optional
:return: The return value of the function 'func'
:rtype: 'not fixed'
"""
if code == 0 or code == 1:
start_time = time.perf_counter()
value = func(*args, **kwargs)
end_time = time.perf_counter()
run_time = end_time - start_time
if timing:
print(f"Finished {func.__name__!r} in {run_time:.4f} secs")
if code == 1:
if isinstance(func, types.BuiltinFunctionType):
print("\nSource code of the function '%s' is not available!" \
%func.__name__)
else:
print("\n" + inspect.getsource(func))
next_level(func)
return value
elif code == 2:
if isinstance(func, types.BuiltinFunctionType):
print("\nSource code of the function '%s' is not available!" \
%func.__name__)
else:
print("\n" + inspect.getsource(func) + "\n")
next_level(func)
return None
sig = inspect.signature(func)
params = list(sig.parameters.values())
param_time = inspect.Parameter("timing",
inspect.Parameter.KEYWORD_ONLY,
default=False)
param_code = inspect.Parameter("code",
inspect.Parameter.KEYWORD_ONLY,
default=0)
if 'kwargs' in list(sig.parameters):
kw_loc = list(sig.parameters).index('kwargs')
params.insert(kw_loc, param_code)
params.insert(kw_loc, param_time)
else:
params.append(param_time)
params.append(param_code)
try:
wrapper.__signature__ = sig.replace(parameters=params)
except:
print(">> Exception:", func.__name__)
print(">>>", params)
return wrapper
[docs]
def addin_all_functions(submod):
"""Applies 'addin' function to all functions of a module at runtime.
:param submod: The target sub-module of which all the functions are inserted
additional features, default to None
:type submod: module
"""
for name, member in inspect.getmembers(submod):
if callable(member) and \
member.__name__ != 'files' and \
name[0].islower():
setattr(submod, name, addin(member))
[docs]
def addin_all(modname='cwordtm'):
"""Applies 'addin' function to all functions of all sub-modules of
a module at runtime.
:param modname: The target module of which all the functions are inserted
additional features, default to 'wordtm'
:type modname: str, optional
"""
module = import_module(modname)
if hasattr(module, "__path__"):
for _, submodname, ispkg in pkgutil.iter_modules(module.__path__):
# if not ispkg and submodname != 'meta':
if not ispkg:
submod = import_module(".." + submodname, \
modname + "." + submodname)
addin_all_functions(submod)
else:
addin_all_functions(module)