Spaces:
Configuration error
Configuration error
import ast | |
import os | |
IGNORE_FUNCTIONS = [ | |
"_format_type", | |
"_remove_additional_properties", | |
"_remove_strict_from_schema", | |
"filter_schema_fields", | |
"text_completion", | |
"_check_for_os_environ_vars", | |
"clean_message", | |
"unpack_defs", | |
"convert_anyof_null_to_nullable", # has a set max depth | |
"add_object_type", | |
"strip_field", | |
"_transform_prompt", | |
"mask_dict", | |
"_serialize", # we now set a max depth for this | |
"_sanitize_request_body_for_spend_logs_payload", # testing added for circular reference | |
"_sanitize_value", # testing added for circular reference | |
"set_schema_property_ordering", # testing added for infinite recursion | |
"process_items", # testing added for infinite recursion + max depth set. | |
"_can_object_call_model", # max depth set. | |
"encode_unserializable_types", # max depth set. | |
"filter_value_from_dict", # max depth set. | |
] | |
class RecursiveFunctionFinder(ast.NodeVisitor): | |
def __init__(self): | |
self.recursive_functions = [] | |
self.ignored_recursive_functions = [] | |
def visit_FunctionDef(self, node): | |
# Check if the function calls itself | |
if any(self._is_recursive_call(node, call) for call in ast.walk(node)): | |
if node.name in IGNORE_FUNCTIONS: | |
self.ignored_recursive_functions.append(node.name) | |
else: | |
self.recursive_functions.append(node.name) | |
self.generic_visit(node) | |
def _is_recursive_call(self, func_node, call_node): | |
# Check if the call node is a function call | |
if not isinstance(call_node, ast.Call): | |
return False | |
# Case 1: Direct function call (e.g., my_func()) | |
if isinstance(call_node.func, ast.Name) and call_node.func.id == func_node.name: | |
return True | |
# Case 2: Method call with self (e.g., self.my_func()) | |
if isinstance(call_node.func, ast.Attribute) and isinstance( | |
call_node.func.value, ast.Name | |
): | |
return ( | |
call_node.func.value.id == "self" | |
and call_node.func.attr == func_node.name | |
) | |
return False | |
def find_recursive_functions_in_file(file_path): | |
with open(file_path, "r") as file: | |
tree = ast.parse(file.read(), filename=file_path) | |
finder = RecursiveFunctionFinder() | |
finder.visit(tree) | |
return finder.recursive_functions, finder.ignored_recursive_functions | |
def find_recursive_functions_in_directory(directory): | |
recursive_functions = {} | |
ignored_recursive_functions = {} | |
for root, _, files in os.walk(directory): | |
for file in files: | |
print("file: ", file) | |
if file.endswith(".py"): | |
file_path = os.path.join(root, file) | |
functions, ignored = find_recursive_functions_in_file(file_path) | |
if functions: | |
recursive_functions[file_path] = functions | |
if ignored: | |
ignored_recursive_functions[file_path] = ignored | |
return recursive_functions, ignored_recursive_functions | |
if __name__ == "__main__": | |
# Example usage | |
# raise exception if any recursive functions are found, except for the ignored ones | |
# this is used in the CI/CD pipeline to prevent recursive functions from being merged | |
directory_path = "./litellm" | |
recursive_functions, ignored_recursive_functions = ( | |
find_recursive_functions_in_directory(directory_path) | |
) | |
print("UNIGNORED RECURSIVE FUNCTIONS: ", recursive_functions) | |
print("IGNORED RECURSIVE FUNCTIONS: ", ignored_recursive_functions) | |
if len(recursive_functions) > 0: | |
# raise exception if any recursive functions are found | |
for file, functions in recursive_functions.items(): | |
print( | |
f"π¨ Unignored recursive functions found in {file}: {functions}. THIS IS REALLY BAD, it has caused CPU Usage spikes in the past. Only keep this if it's ABSOLUTELY necessary." | |
) | |
file, functions = list(recursive_functions.items())[0] | |
raise Exception( | |
f"π¨ Unignored recursive functions found include {file}: {functions}. THIS IS REALLY BAD, it has caused CPU Usage spikes in the past. Only keep this if it's ABSOLUTELY necessary." | |
) | |