a7d6d88f6f
CI / changes (push) Has been cancelled
CI / cd libs/checkpoint (push) Has been cancelled
CI / cd libs/checkpoint-conformance (push) Has been cancelled
CI / cd libs/checkpoint-postgres (push) Has been cancelled
CI / cd libs/checkpoint-sqlite (push) Has been cancelled
CI / cd libs/cli (push) Has been cancelled
CI / cd libs/prebuilt (push) Has been cancelled
CI / cd libs/sdk-py (push) Has been cancelled
CI / cd libs/langgraph (push) Has been cancelled
CI / Check SDK methods matching (push) Has been cancelled
CI / Check CLI schema hasn't changed #3.13 (push) Has been cancelled
CI / CLI integration test (push) Has been cancelled
CI / sdk-py integration test (push) Has been cancelled
CI / CI Success (push) Has been cancelled
baseline / benchmark (push) Has been cancelled
Deploy Redirects to GitHub Pages / deploy (push) Has been cancelled
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
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()
|