-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Python: Fix executor handler type resolution when using from __future__ import annotations
#4317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+139
−4
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
15667cc
Python: Fix Executor handler type checking with __future__ annotation…
moonbox3 c90b1ca
Address PR review feedback for #3898: improve error handling and test…
moonbox3 2bb413a
Narrow exception catch and add test for unresolvable annotations (#3898)
moonbox3 8cdc36a
Fix #3898: fall back to raw annotations when get_type_hints fails
moonbox3 0e61c00
Fix test to match new fallback behavior when get_type_hints fails (#3…
moonbox3 b66344e
Apply pyupgrade: remove unnecessary string annotation quote
moonbox3 269aca8
Add noqa for intentionally undefined name in annotation test
moonbox3 7cae335
Merge branch 'main' into agent/fix-3898-2
moonbox3 8f34154
Merge branch 'main' into agent/fix-3898-2
moonbox3 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
python/packages/core/tests/workflow/test_executor_future.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| # Copyright (c) Microsoft. All rights reserved. | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| import pytest | ||
| from pydantic import BaseModel | ||
|
|
||
| from agent_framework import Executor, WorkflowContext, handler | ||
|
|
||
|
|
||
| class MyTypeA(BaseModel): | ||
| pass | ||
|
|
||
|
|
||
| class MyTypeB(BaseModel): | ||
| pass | ||
|
|
||
|
|
||
| class MyTypeC(BaseModel): | ||
| pass | ||
|
|
||
|
|
||
| class TestExecutorFutureAnnotations: | ||
| """Test suite for Executor with from __future__ import annotations.""" | ||
|
|
||
| def test_handler_decorator_future_annotations(self): | ||
| """Test @handler decorator works with stringified annotations (issue #3898).""" | ||
|
|
||
| class MyExecutor(Executor): | ||
| @handler | ||
| async def example(self, input: str, ctx: WorkflowContext[MyTypeA, MyTypeB]) -> None: | ||
| pass | ||
|
|
||
| exec_instance = MyExecutor(id="test") | ||
| assert str in exec_instance._handlers | ||
| spec = exec_instance._handler_specs[0] | ||
| assert spec["message_type"] is str | ||
| assert spec["output_types"] == [MyTypeA] | ||
| assert spec["workflow_output_types"] == [MyTypeB] | ||
|
|
||
| def test_handler_decorator_future_annotations_single_type_arg(self): | ||
| """Test @handler with single type argument and future annotations.""" | ||
|
|
||
| class MyExecutor(Executor): | ||
| @handler | ||
| async def example(self, input: int, ctx: WorkflowContext[MyTypeA]) -> None: | ||
| pass | ||
|
|
||
| exec_instance = MyExecutor(id="test") | ||
| assert int in exec_instance._handlers | ||
| spec = exec_instance._handler_specs[0] | ||
| assert spec["message_type"] is int | ||
| assert spec["output_types"] == [MyTypeA] | ||
|
|
||
| def test_handler_decorator_future_annotations_complex(self): | ||
| """Test @handler with complex type annotations and future annotations.""" | ||
|
|
||
| class MyExecutor(Executor): | ||
| @handler | ||
| async def example(self, data: dict[str, Any], ctx: WorkflowContext[list[str]]) -> None: | ||
| pass | ||
|
|
||
| exec_instance = MyExecutor(id="test") | ||
| spec = exec_instance._handler_specs[0] | ||
| assert spec["message_type"] == dict[str, Any] | ||
| assert spec["output_types"] == [list[str]] | ||
|
|
||
| def test_handler_decorator_future_annotations_bare_context(self): | ||
| """Test @handler with bare WorkflowContext and future annotations.""" | ||
|
|
||
| class MyExecutor(Executor): | ||
| @handler | ||
| async def example(self, input: str, ctx: WorkflowContext) -> None: | ||
| pass | ||
|
|
||
| exec_instance = MyExecutor(id="test") | ||
| assert str in exec_instance._handlers | ||
moonbox3 marked this conversation as resolved.
Show resolved
Hide resolved
moonbox3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| spec = exec_instance._handler_specs[0] | ||
| assert spec["output_types"] == [] | ||
| assert spec["workflow_output_types"] == [] | ||
|
|
||
| def test_handler_decorator_future_annotations_explicit_types(self): | ||
| """Test @handler with explicit type parameters under future annotations.""" | ||
|
|
||
| class MyExecutor(Executor): | ||
| @handler(input=str, output=MyTypeA) | ||
| async def example(self, input, ctx) -> None: | ||
| pass | ||
|
|
||
| exec_instance = MyExecutor(id="test") | ||
| assert str in exec_instance._handlers | ||
| spec = exec_instance._handler_specs[0] | ||
| assert spec["message_type"] is str | ||
| assert spec["output_types"] == [MyTypeA] | ||
|
|
||
| def test_handler_decorator_future_annotations_union_context(self): | ||
| """Test @handler with union type context annotations and future annotations.""" | ||
|
|
||
| class MyExecutor(Executor): | ||
| @handler | ||
| async def example(self, input: str, ctx: WorkflowContext[MyTypeA | MyTypeB, MyTypeC]) -> None: | ||
| pass | ||
|
|
||
| exec_instance = MyExecutor(id="test") | ||
| assert str in exec_instance._handlers | ||
| spec = exec_instance._handler_specs[0] | ||
| assert spec["output_types"] == [MyTypeA, MyTypeB] | ||
| assert spec["workflow_output_types"] == [MyTypeC] | ||
|
|
||
| def test_handler_unresolvable_annotation_raises(self): | ||
| """Test that an unresolvable forward-reference annotation raises ValueError. | ||
|
|
||
| When get_type_hints fails (e.g. NameError for NonExistentType), the code falls back | ||
| to raw string annotations. The ctx parameter's raw string annotation is then not | ||
| recognised as a valid WorkflowContext type, so a ValueError is still raised. | ||
| """ | ||
| with pytest.raises(ValueError): | ||
|
|
||
| class Bad(Executor): | ||
| @handler | ||
| async def example(self, input: NonExistentType, ctx: WorkflowContext[MyTypeA, MyTypeB]) -> None: # noqa: F821 | ||
| pass | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.