This is an automated email from the ASF dual-hosted git repository.

wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git


The following commit(s) were added to refs/heads/main by this push:
     new 63e67226 [python] warn when an action declares a return value (#835)
63e67226 is described below

commit 63e67226612130b9f74ccddf8d437825fefdbd4c
Author: vishnu prakash <[email protected]>
AuthorDate: Mon Aug 3 08:54:06 2026 +0530

    [python] warn when an action declares a return value (#835)
---
 python/flink_agents/plan/actions/action.py        |  2 +-
 python/flink_agents/plan/function.py              | 26 ++++++++++++++++
 python/flink_agents/plan/tests/test_action.py     | 36 +++++++++++++++++++++++
 python/flink_agents/plan/tests/test_agent_plan.py | 26 ++++++++++++++++
 4 files changed, 89 insertions(+), 1 deletion(-)

diff --git a/python/flink_agents/plan/actions/action.py 
b/python/flink_agents/plan/actions/action.py
index 9d57ae7c..efdf4855 100644
--- a/python/flink_agents/plan/actions/action.py
+++ b/python/flink_agents/plan/actions/action.py
@@ -51,7 +51,6 @@ class Action(BaseModel):
     model_config = ConfigDict(arbitrary_types_allowed=True)
 
     name: str
-    # TODO: Raise a warning when the action has a return value, as it will be 
ignored.
     exec: PythonFunction | JavaFunction
     trigger_conditions: List[str]
     config: Dict[str, Any] | None = None
@@ -124,3 +123,4 @@ class Action(BaseModel):
         )
         # TODO: Update expected signature after import State and Context.
         self.exec.check_signature(Event, RunnerContext)
+        self.exec.warn_if_returns_value(self.name)
diff --git a/python/flink_agents/plan/function.py 
b/python/flink_agents/plan/function.py
index 28bc5089..7988084a 100644
--- a/python/flink_agents/plan/function.py
+++ b/python/flink_agents/plan/function.py
@@ -108,6 +108,14 @@ class Function(BaseModel, ABC):
     def check_signature(self, *args: Tuple[Any, ...]) -> None:
         """Check function signature is legal or not."""
 
+    def warn_if_returns_value(self, action_name: str) -> None:
+        """Warn if this function declares a non-``None`` return annotation.
+
+        Action return values are discarded by the framework, so declaring a
+        return type is almost always a mistake. Only Python callables can be
+        introspected, so non-Python function types treat this as a no-op.
+        """
+
     @abstractmethod
     def __call__(self, *args: Tuple[Any, ...], **kwargs: Dict[str, Any]) -> 
Any:
         """Execute function."""
@@ -191,6 +199,24 @@ class PythonFunction(Function):
         except TypeError as e:
             raise TypeError(err_msg) from e
 
+    def warn_if_returns_value(self, action_name: str) -> None:
+        """Warn that an action's return value is ignored by the framework.
+
+        Actions communicate by sending events via ``ctx.send_event(...)``; any
+        value they return is discarded. A non-``None`` return annotation is
+        almost always a mistake, so surface it when the plan is built. The
+        stringized ``"None"`` is accepted for modules that opt into
+        ``from __future__ import annotations``.
+        """
+        return_annotation = 
inspect.signature(self.__get_func()).return_annotation
+        if return_annotation not in (inspect.Signature.empty, None, 
type(None), "None"):
+            logger.warning(
+                f"Action '{action_name}' declares return type "
+                f"'{return_annotation}', but action return values are ignored 
by "
+                f"the framework. Actions should send events via "
+                f"ctx.send_event(...) and return None."
+            )
+
     def __call__(self, *args: Tuple[Any, ...], **kwargs: Dict[str, Any]) -> 
Any:
         """Execute the stored function with provided arguments.
 
diff --git a/python/flink_agents/plan/tests/test_action.py 
b/python/flink_agents/plan/tests/test_action.py
index 08f0d5dd..da097d04 100644
--- a/python/flink_agents/plan/tests/test_action.py
+++ b/python/flink_agents/plan/tests/test_action.py
@@ -16,6 +16,7 @@
 # limitations under the License.
 
#################################################################################
 import json
+import logging
 from pathlib import Path
 
 import pytest
@@ -36,6 +37,10 @@ def illegal_signature(value: int, ctx: RunnerContext) -> 
None:
     pass
 
 
+def returns_value(event: Event, ctx: RunnerContext) -> str:
+    return "ignored by the framework"
+
+
 def test_action_signature_legal() -> None:
     Action(
         name="legal",
@@ -53,6 +58,37 @@ def test_action_signature_illegal() -> None:
         )
 
 
+def test_action_warns_when_returns_value(
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    with caplog.at_level(logging.WARNING):
+        Action(
+            name="returns_value",
+            exec=PythonFunction.from_callable(returns_value),
+            trigger_conditions=[InputEvent.EVENT_TYPE],
+        )
+    assert any(
+        "returns_value" in record.getMessage()
+        and "ignored" in record.getMessage().lower()
+        for record in caplog.records
+    )
+
+
+def test_action_no_warning_when_returns_none(
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    with caplog.at_level(logging.WARNING):
+        Action(
+            name="legal",
+            exec=PythonFunction.from_callable(legal_signature),
+            trigger_conditions=[InputEvent.EVENT_TYPE],
+        )
+    assert not any(
+        "ignored by the framework" in record.getMessage()
+        for record in caplog.records
+    )
+
+
 @pytest.fixture(scope="module")
 def action() -> Action:
     func = PythonFunction.from_callable(legal_signature)
diff --git a/python/flink_agents/plan/tests/test_agent_plan.py 
b/python/flink_agents/plan/tests/test_agent_plan.py
index 04fdb6c7..c92e36b0 100644
--- a/python/flink_agents/plan/tests/test_agent_plan.py
+++ b/python/flink_agents/plan/tests/test_agent_plan.py
@@ -16,6 +16,7 @@
 # limitations under the License.
 
#################################################################################
 import json
+import logging
 from pathlib import Path
 from typing import Any, ClassVar, Dict, List, Sequence
 
@@ -565,6 +566,31 @@ def test_add_action_and_resource_to_agent() -> None:
     assert actual == expected
 
 
+def _returns_value_action(event: Event, ctx: RunnerContext) -> str:
+    return "ignored by the framework"
+
+
+def test_warns_for_returning_action_added_via_add_action(
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    """Actions registered imperatively via add_action are also checked, since
+    the warning now lives in Action.__init__ rather than a single collection 
path.
+    """
+    agent = Agent()
+    agent.add_action(
+        name="returns_value",
+        trigger_conditions=[InputEvent.EVENT_TYPE],
+        func=_returns_value_action,
+    )
+    with caplog.at_level(logging.WARNING):
+        AgentPlan.from_agent(agent, AgentConfiguration())
+    assert any(
+        "returns_value" in record.getMessage()
+        and "ignored" in record.getMessage().lower()
+        for record in caplog.records
+    )
+
+
 # ── String identifier tests ──────────────────────────────────────────────
 
 

Reply via email to