import ast import os from itertools import filterfalse from typing import Dict, List, Tuple ROOT_PATH = os.path.abspath(os.path.join(__file__, "..", "..", "..")) CLIENT_PATH = os.path.join(ROOT_PATH, "libs", "sdk-py", "langgraph_sdk", "client.py") ASYNC_TO_SYNC_METHOD_MAP: Dict[str, str] = { "aclose": "close", "__aenter__": "__enter__", "__aexit__": "__exit__", } def get_class_methods(node: ast.ClassDef) -> List[str]: return [n.name for n in node.body if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))] def find_classes(tree: ast.AST) -> List[Tuple[str, List[str]]]: classes = [] for node in ast.walk(tree): if isinstance(node, ast.ClassDef): methods = get_class_methods(node) classes.append((node.name, methods)) return classes def compare_sync_async_methods(sync_methods: List[str], async_methods: List[str]) -> List[str]: sync_set = set(sync_methods) async_set = {ASYNC_TO_SYNC_METHOD_MAP.get(async_method, async_method) for async_method in async_methods} missing_in_sync = list(async_set - sync_set) missing_in_async = list(sync_set - async_set) return missing_in_sync + missing_in_async def main(): with open(CLIENT_PATH, "r") as file: tree = ast.parse(file.read()) classes = find_classes(tree) def is_sync(class_spec: Tuple[str, List[str]]) -> bool: return class_spec[0].startswith("Sync") sync_class_name_to_methods = {class_name: class_methods for class_name, class_methods in filter(is_sync, classes)} async_class_name_to_methods = {class_name: class_methods for class_name, class_methods in filterfalse(is_sync, classes)} mismatches = [] for async_class_name, async_class_methods in async_class_name_to_methods.items(): sync_class_name = "Sync" + async_class_name sync_class_methods = sync_class_name_to_methods.get(sync_class_name, []) diff = compare_sync_async_methods(sync_class_methods, async_class_methods) if diff: mismatches.append((sync_class_name, async_class_name, diff)) if mismatches: error_message = "Mismatches found between sync and async client methods:\n" for sync_class_name, async_class_name, diff in mismatches: error_message += f"{sync_class_name} vs {async_class_name}:\n" for method in diff: error_message += f" - {method}\n" raise ValueError(error_message) print("All sync and async client methods match.") if __name__ == "__main__": main()