mirror of
https://github.com/tiennm99/litellm.git
synced 2026-06-17 14:48:44 +00:00
170 lines
6.4 KiB
Python
170 lines
6.4 KiB
Python
import ast
|
|
import os
|
|
|
|
|
|
def check_for_litellm_module_deletion(base_dir):
|
|
"""
|
|
Checks for code patterns that delete litellm modules from sys.modules
|
|
in the test_litellm directory.
|
|
|
|
Specifically looks for patterns like:
|
|
for module in list(sys.modules.keys()):
|
|
if module.startswith("litellm"):
|
|
del sys.modules[module]
|
|
"""
|
|
problematic_files = []
|
|
test_dir = os.path.join(base_dir, "test_litellm")
|
|
|
|
if not os.path.exists(test_dir):
|
|
print(f"Warning: Directory {test_dir} does not exist.")
|
|
return []
|
|
|
|
print(f"Checking directory: {test_dir}")
|
|
|
|
for root, _, files in os.walk(test_dir):
|
|
for file in files:
|
|
if file.endswith(".py"):
|
|
file_path = os.path.join(root, file)
|
|
try:
|
|
with open(file_path, "r") as f:
|
|
tree = ast.parse(f.read())
|
|
except SyntaxError:
|
|
print(f"Warning: Syntax error in file {file_path}")
|
|
continue
|
|
|
|
# Check for litellm module deletion patterns
|
|
if has_litellm_module_deletion(tree):
|
|
relative_path = os.path.relpath(file_path, base_dir)
|
|
problematic_files.append(relative_path)
|
|
print(f"Found litellm module deletion in: {relative_path}")
|
|
|
|
return problematic_files
|
|
|
|
|
|
def has_litellm_module_deletion(tree):
|
|
"""
|
|
Checks if the AST contains patterns that delete litellm modules from sys.modules.
|
|
|
|
Looks for:
|
|
1. Loops over sys.modules.keys()
|
|
2. Conditions checking if module startswith "litellm"
|
|
3. del sys.modules[module] statements
|
|
"""
|
|
class LiteLLMDeletionVisitor(ast.NodeVisitor):
|
|
def __init__(self):
|
|
self.has_sys_modules_loop = False
|
|
self.has_litellm_check = False
|
|
self.has_del_sys_modules = False
|
|
self.current_for_target = None
|
|
|
|
def visit_For(self, node):
|
|
# Check if we're looping over sys.modules.keys()
|
|
if (isinstance(node.iter, ast.Call) and
|
|
isinstance(node.iter.func, ast.Attribute) and
|
|
isinstance(node.iter.func.value, ast.Attribute) and
|
|
isinstance(node.iter.func.value.value, ast.Name) and
|
|
node.iter.func.value.value.id == "sys" and
|
|
node.iter.func.value.attr == "modules" and
|
|
node.iter.func.attr == "keys"):
|
|
|
|
self.has_sys_modules_loop = True
|
|
if isinstance(node.target, ast.Name):
|
|
self.current_for_target = node.target.id
|
|
|
|
# Check the body of the for loop
|
|
for stmt in node.body:
|
|
self.visit(stmt)
|
|
|
|
# Also check for list(sys.modules.keys()) pattern
|
|
elif (isinstance(node.iter, ast.Call) and
|
|
isinstance(node.iter.func, ast.Name) and
|
|
node.iter.func.id == "list" and
|
|
len(node.iter.args) == 1 and
|
|
isinstance(node.iter.args[0], ast.Call) and
|
|
isinstance(node.iter.args[0].func, ast.Attribute) and
|
|
isinstance(node.iter.args[0].func.value, ast.Attribute) and
|
|
isinstance(node.iter.args[0].func.value.value, ast.Name) and
|
|
node.iter.args[0].func.value.value.id == "sys" and
|
|
node.iter.args[0].func.value.attr == "modules" and
|
|
node.iter.args[0].func.attr == "keys"):
|
|
|
|
self.has_sys_modules_loop = True
|
|
if isinstance(node.target, ast.Name):
|
|
self.current_for_target = node.target.id
|
|
|
|
# Check the body of the for loop
|
|
for stmt in node.body:
|
|
self.visit(stmt)
|
|
|
|
self.generic_visit(node)
|
|
|
|
def visit_If(self, node):
|
|
# Check for conditions like module.startswith("litellm")
|
|
if (isinstance(node.test, ast.Call) and
|
|
isinstance(node.test.func, ast.Attribute) and
|
|
isinstance(node.test.func.value, ast.Name) and
|
|
node.test.func.value.id == self.current_for_target and
|
|
node.test.func.attr == "startswith" and
|
|
len(node.test.args) == 1 and
|
|
isinstance(node.test.args[0], ast.Constant) and
|
|
node.test.args[0].value == "litellm"):
|
|
|
|
self.has_litellm_check = True
|
|
|
|
# Check the body of the if statement
|
|
for stmt in node.body:
|
|
self.visit(stmt)
|
|
|
|
self.generic_visit(node)
|
|
|
|
def visit_Delete(self, node):
|
|
# Check for del sys.modules[module]
|
|
for target in node.targets:
|
|
if (isinstance(target, ast.Subscript) and
|
|
isinstance(target.value, ast.Attribute) and
|
|
isinstance(target.value.value, ast.Name) and
|
|
target.value.value.id == "sys" and
|
|
target.value.attr == "modules" and
|
|
isinstance(target.slice, ast.Name) and
|
|
target.slice.id == self.current_for_target):
|
|
|
|
self.has_del_sys_modules = True
|
|
|
|
self.generic_visit(node)
|
|
|
|
visitor = LiteLLMDeletionVisitor()
|
|
visitor.visit(tree)
|
|
|
|
return (visitor.has_sys_modules_loop and
|
|
visitor.has_litellm_check and
|
|
visitor.has_del_sys_modules)
|
|
|
|
|
|
def main():
|
|
"""
|
|
Main function to check for litellm module deletion patterns in test files.
|
|
"""
|
|
# local dir
|
|
#tests_dir = "../../tests/"
|
|
|
|
# ci/cd dir
|
|
tests_dir = "./tests/"
|
|
|
|
problematic_files = check_for_litellm_module_deletion(tests_dir)
|
|
|
|
if problematic_files:
|
|
print("\nERROR: Found files that delete litellm modules from sys.modules:")
|
|
for file_path in problematic_files:
|
|
print(f" - {file_path}")
|
|
|
|
raise Exception(
|
|
f"Found {len(problematic_files)} file(s) that delete litellm modules from sys.modules. "
|
|
f"This can cause import issues and test failures. Files: {problematic_files}"
|
|
)
|
|
else:
|
|
print("✓ No litellm module deletion patterns found in test_litellm directory.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|