Skip to content

fix(fns): resolve return annotations with from __future__ import annotations#1326

Closed
CrepuscularIRIS wants to merge 1 commit into
PrefectHQ:mainfrom
CrepuscularIRIS:fix/future-annotations-return-type
Closed

fix(fns): resolve return annotations with from __future__ import annotations#1326
CrepuscularIRIS wants to merge 1 commit into
PrefectHQ:mainfrom
CrepuscularIRIS:fix/future-annotations-return-type

Conversation

@CrepuscularIRIS

Copy link
Copy Markdown

Summary

Fixes #950

When from __future__ import annotations (PEP 563) is active in a module, all type annotations become strings at runtime. PythonFunction.from_function() reads sig.return_annotation directly, which returns a string like "Recipe" instead of the actual Recipe class. This causes @marvin.fn decorated functions to produce string outputs instead of typed Pydantic model instances.

Root Cause

inspect.signature(func).return_annotation returns the raw annotation value. Under PEP 563, annotations are stored as strings and not evaluated until explicitly resolved. The fix at src/marvin/utilities/types.py:372 was using this raw value without resolution.

Changes

  • src/marvin/utilities/types.py: Add _resolve_return_annotation() helper that uses typing.get_type_hints() to resolve stringified annotations, with a fallback to sig.return_annotation when resolution fails (handles unresolvable forward refs, built-in functions, etc.)
  • tests/basic/utilities/test_types.py: Add 4 tests covering: future annotations resolution, normal annotations, no annotation, and unresolvable forward ref fallback
  • tests/basic/utilities/_future_annotations_helper.py: Test helper module with from __future__ import annotations active

Testing

  • All 263 existing + new tests pass (no regressions)
  • Bug reproduction confirmed fixed
  • Ruff lint passes
  • Unresolvable forward ref gracefully falls back
# Verify:
uv run pytest tests/basic/utilities/test_types.py -v

Notes

Minimal fix — only changes the annotation resolution path. No unrelated changes. The approach follows the same typing.get_type_hints() strategy suggested in #1258. The exception handler is narrowed to KeyError | NameError | AttributeError | TypeError to avoid masking unexpected errors.

…otations`

When PEP 563 (`from __future__ import annotations`) is active, all type
annotations become strings. `PythonFunction.from_function()` was reading
`sig.return_annotation` directly, which returned a string like "Recipe"
instead of the actual class. This caused `@marvin.fn` decorated functions
to return strings instead of typed objects.

Use `typing.get_type_hints()` to resolve stringified annotations back to
their actual types, with a fallback to the original behavior when
resolution fails (e.g., unresolvable forward references).

Fixes PrefectHQ#950
@github-actions github-actions Bot added the tests label Apr 14, 2026
CrepuscularIRIS added a commit to CrepuscularIRIS/Beatless that referenced this pull request Apr 14, 2026
- auto-pr.sh: Real submission pipeline (discover → fix → review → PR)
- github-pr-state.json: 0.5h interval, lock file prevents concurrency
- github-hunt: DISABLED in heartbeat-driver
- First real PR submitted: PrefectHQ/marvin#1326

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@CrepuscularIRIS

Copy link
Copy Markdown
Author

Closing this PR — no maintainer activity in 2+ weeks. Happy to reopen or resubmit if you'd like to revisit. Thanks for the project!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

from __future__ import annotations causes ai functions to return strings

1 participant