da-daken commented on code in PR #579: URL: https://github.com/apache/flink-agents/pull/579#discussion_r3031842749
########## tools/check_resource_consistency.py: ########## @@ -0,0 +1,340 @@ +#!/usr/bin/env python3 +################################################################################ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +################################################################################ +""" +Check the consistency of resource class name constants between ResourceName.java and resource.py +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + + +def parse_java_resource_name(java_path: Path) -> dict: + content = java_path.read_text(encoding="utf-8") + + string_const_re = re.compile( + r'public\s+static\s+final\s+String\s+([A-Za-z0-9_]+)\s*=\s*"([^"]+)";', + re.DOTALL, + ) + class_re = re.compile( + r"public\s+(?:static\s+)?final\s+class\s+(\w+)\s*\{" + ) + + class_stack = [] + brace_depth = 0 + result = {} + pos = 0 + + while True: + next_class = class_re.search(content, pos) + next_string = string_const_re.search(content, pos) + next_open = content.find("{", pos) + next_close = content.find("}", pos) + + best_pos = len(content) + next_event = None + if next_class and next_class.start() < best_pos: + best_pos = next_class.start() + next_event = ("class", next_class) + if next_string and next_string.start() < best_pos: + best_pos = next_string.start() + next_event = ("string", next_string) + if next_open >= 0 and next_open < best_pos: + best_pos = next_open + next_event = ("open", None) + if next_close >= 0 and next_close < best_pos: + best_pos = next_close + next_event = ("close", None) + + if next_event is None: + break + + kind, match = next_event + pos = best_pos + 1 + + if kind == "class" and match: + name = match.group(1) + brace_depth += 1 + if name == "ResourceName": + class_stack = [("ResourceName", 1)] + elif class_stack: + class_stack.append((name, brace_depth)) + pos = match.end() + elif kind == "string" and match and class_stack: + const_name, value = match.group(1), match.group(2) + if value and value != "DECIDE_IN_RUNTIME_MCPServer": + path = [c[0] for c in class_stack] + + if len(path) == 2: + key = (path[1], "Java") + if key not in result: + result[key] = {} + result[key][const_name] = value + elif len(path) == 3 and path[2] == "Python": + key = (path[1], "Python") + if key not in result: + result[key] = {} + result[key][const_name] = value + pos = match.end() + elif kind == "open": + brace_depth += 1 + pos = next_open + 1 + elif kind == "close": + brace_depth -= 1 + if class_stack and brace_depth == class_stack[-1][1] - 1: + class_stack.pop() + pos = next_close + 1 + + return result + + +def get_python_resource_name_map(python_path: Path) -> dict: + root = python_path.parent.parent + python_dir = root / "python" + + try: + if str(python_dir) not in sys.path: + sys.path.insert(0, str(python_dir)) + for p in python_dir.glob(".venv/lib/python*/site-packages"): + if str(p) not in sys.path: + sys.path.insert(0, str(p)) + break + from flink_agents.api.resource import ResourceName + + python_map = {} + for resource_name in ["ChatModel", "EmbeddingModel", "VectorStore"]: + if not hasattr(ResourceName, resource_name): + continue + resource_cls = getattr(ResourceName, resource_name) + py_impls = { + attr: getattr(resource_cls, attr) + for attr in dir(resource_cls) + if not attr.startswith("_") + and isinstance(getattr(resource_cls, attr), str) + } + if py_impls: + python_map[(resource_name, "Python")] = py_impls + if hasattr(resource_cls, "Java"): + java_impls = { + attr: getattr(resource_cls.Java, attr) + for attr in dir(resource_cls.Java) + if not attr.startswith("_") + and isinstance(getattr(resource_cls.Java, attr), str) + } + if java_impls: + python_map[(resource_name, "Java")] = java_impls + if hasattr(ResourceName, "MCP_SERVER"): + python_map[("MCP", "Python")] = {"MCP_SERVER": ResourceName.MCP_SERVER} + return python_map + except ImportError: + return _parse_python_resource_name(python_path) + + +def _parse_python_resource_name(python_path: Path) -> dict: + content = python_path.read_text(encoding="utf-8") + string_const_re = re.compile(r'^\s+([A-Za-z0-9_]+)\s*=\s*"([^"]+)"\s*$') + class_re = re.compile(r"class\s+(\w+)\s*:") + + result = {} + indent_stack = [] # [(indent, ["ResourceName", "ChatModel", ...])] + base_indent = -1 + + for line in content.split("\n"): + if "class ResourceName:" in line: + base_indent = len(line) - len(line.lstrip()) + indent_stack = [(base_indent, ["ResourceName"])] + continue + if base_indent < 0: + continue + + indent = len(line) - len(line.lstrip()) + stripped = line.strip() + + while len(indent_stack) > 1 and indent <= indent_stack[-1][0] and stripped: + indent_stack.pop() + + if stripped.startswith("class ") and class_re.match(stripped): + name = class_re.match(stripped).group(1) + if indent == base_indent + 4: + indent_stack = [(base_indent, ["ResourceName"])] + indent_stack.append((indent, ["ResourceName", name])) + elif indent == base_indent + 8 and len(indent_stack) >= 2: + parent_path = indent_stack[-1][1] + indent_stack.append((indent, parent_path + [name])) + + path = indent_stack[-1][1] if indent_stack else [] + m = string_const_re.match(line) + if m and path: + const_name, value = m.group(1), m.group(2) + if value: + key = ".".join(path) + if key not in result: + result[key] = {} + result[key][const_name] = value + + python_map = {} + for key, consts in result.items(): + parts = key.split(".") + if len(parts) >= 2 and parts[0] == "ResourceName": + rt = parts[1] + if len(parts) == 2: + python_map[(rt, "Python")] = consts + elif len(parts) == 3 and parts[2] == "Java": + python_map[(rt, "Java")] = consts + if "ResourceName" in result and "MCP_SERVER" in result["ResourceName"]: + python_map[("MCP", "Python")] = {"MCP_SERVER": result["ResourceName"]["MCP_SERVER"]} + return python_map + + +_JAVA_ONLY_NAMES = frozenset({ + "PYTHON_WRAPPER_CONNECTION", "PYTHON_WRAPPER_SETUP", + "PYTHON_WRAPPER_VECTOR_STORE", "PYTHON_WRAPPER_COLLECTION_MANAGEABLE_VECTOR_STORE", +}) +_PYTHON_ONLY_NAMES = frozenset({ + "JAVA_WRAPPER_CONNECTION", "JAVA_WRAPPER_SETUP", + "JAVA_WRAPPER_VECTOR_STORE", "JAVA_WRAPPER_COLLECTION_MANAGEABLE_VECTOR_STORE", +}) +_NAME_ALIASES = { + ("OPENAI_COMPLETIONS_CONNECTION", "OPENAI_CONNECTION"), + ("OPENAI_COMPLETIONS_SETUP", "OPENAI_SETUP"), +} + Review Comment: thanks for your review, you are right ,I saw that the code after 3.13 fixed that naming issue which I didn't notice, I will add a naming inconsistency check to the code. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
