diff --git a/.github/scripts/check_sdk_methods.py b/.github/scripts/check_sdk_methods.py new file mode 100644 index 000000000..6a7d18bf0 --- /dev/null +++ b/.github/scripts/check_sdk_methods.py @@ -0,0 +1,64 @@ +import ast +import os +from itertools import filterfalse +from typing import 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") + + +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 = set(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() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 360c878b2..1af6acc7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,6 +66,18 @@ jobs: uses: ./.github/workflows/_test_scheduler_kafka.yml secrets: inherit + check-sdk-methods: + name: "Check SDK methods matching" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Run check_sdk_methods script + run: python .github/scripts/check_sdk_methods.py + integration-test: name: CLI integration test uses: ./.github/workflows/_integration_test.yml