import ast import os pass_through_classes = [ "AnthropicPassthroughLoggingHandler", ] def get_function_names_from_file(file_path): """ Extracts all function names from a given Python file. """ with open(file_path, "r") as file: tree = ast.parse(file.read()) function_names = [] for node in tree.body: if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): function_names.append(node.name) elif isinstance(node, ast.ClassDef): if node.name in pass_through_classes: for class_node in node.body: if isinstance(class_node, (ast.FunctionDef, ast.AsyncFunctionDef)): function_names.append(class_node.name) return function_names def get_all_functions_called_in_tests(base_dir): """ Returns a set of function names that are called in test functions inside 'local_testing' and 'router_unit_test' directories, specifically in files containing the word 'router'. """ called_functions = set() test_dirs = ["pass_through_unit_tests"] for test_dir in test_dirs: dir_path = os.path.join(base_dir, test_dir) if not os.path.exists(dir_path): print(f"Warning: Directory {dir_path} does not exist.") continue print("dir_path: ", dir_path) for root, _, files in os.walk(dir_path): for file in files: if file.endswith(".py"): print("file: ", file) file_path = os.path.join(root, file) with open(file_path, "r") as f: try: tree = ast.parse(f.read()) except SyntaxError: print(f"Warning: Syntax error in file {file_path}") continue for node in ast.walk(tree): if isinstance(node, ast.Call): if isinstance(node.func, ast.Attribute) and isinstance( node.func.value, ast.Name ): if node.func.value.id in pass_through_classes: # If it's called via the class, add both the original and non-underscore versions method_name = node.func.attr called_functions.add(method_name) if method_name.startswith("_"): called_functions.add(method_name.lstrip("_")) return called_functions def get_functions_from_router(file_path): """ Extracts all functions defined in router.py. """ return get_function_names_from_file(file_path) ignored_function_names = [ "__init__", ] def main(): router_file = [ "../../litellm/proxy/pass_through_endpoints/llm_provider_handlers/anthropic_passthrough_logging_handler.py", ] tests_dir = "../../tests/" router_functions = [] for file in router_file: router_functions.extend(get_functions_from_router(file)) print("router_functions: ", router_functions) called_functions_in_tests = get_all_functions_called_in_tests(tests_dir) print("called_functions_in_tests: ", called_functions_in_tests) untested_functions = [] for fn in router_functions: # Check if the function is called either with or without leading underscore clean_name = fn.lstrip("_") if ( fn not in called_functions_in_tests and clean_name not in called_functions_in_tests ): untested_functions.append(fn) if untested_functions: all_untested_functions = [] for func in untested_functions: if func not in ignored_function_names: all_untested_functions.append(func) untested_perc = (len(all_untested_functions)) / len(router_functions) print("untested_perc: ", untested_perc) if untested_perc > 0: print("The following functions in pass_through/ are not tested:") raise Exception( f"{untested_perc * 100:.2f}% of functions in pass_through/ are not tested: {all_untested_functions}" ) else: print("All functions in router.py are covered by tests.") if __name__ == "__main__": main()