diff --git a/CHANGELOG.md b/CHANGELOG.md index 935907e..fb54904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- `tilebox-workflows`: Allow task `execute()` methods to use a `-> None` return annotation when postponed annotation + evaluation is enabled. + ## [0.55.0] - 2026-07-01 ### Added diff --git a/tilebox-workflows/tests/test_task.py b/tilebox-workflows/tests/test_task.py index dc3f873..7b8bc42 100644 --- a/tilebox-workflows/tests/test_task.py +++ b/tilebox-workflows/tests/test_task.py @@ -1,3 +1,5 @@ +import __future__ + import json from dataclasses import dataclass from typing import Annotated @@ -46,6 +48,35 @@ def execute(self, context: ExecutionContext) -> None: assert TaskMeta.for_task(SimpleTask).executable is True +def _compile_task_with_postponed_return_annotation(return_annotation: str) -> type: + source = f""" +class PostponedAnnotationsTask(Task): + def execute(self, context: ExecutionContext) -> {return_annotation}: + pass +""" + namespace: dict[str, type] = {"Task": Task, "ExecutionContext": ExecutionContext} + code = compile( + source, + filename="", + mode="exec", + flags=__future__.annotations.compiler_flag, + dont_inherit=True, + ) + exec(code, namespace) # noqa: S102 + return namespace["PostponedAnnotationsTask"] + + +def test_task_validation_execute_none_return_type_with_postponed_annotations() -> None: + task_class = _compile_task_with_postponed_return_annotation("None") + + assert TaskMeta.for_task(task_class).executable is True + + +def test_task_validation_execute_invalid_return_type_with_postponed_annotations() -> None: + with pytest.raises(TypeError, match="to not have a return value"): + _compile_task_with_postponed_return_annotation("int") + + def test_task_validation_execute_invalid_signature_no_params() -> None: with pytest.raises(TypeError, match="Expected a function signature of"): # validation happens at class creation time, that's why we create it in a function diff --git a/tilebox-workflows/tilebox/workflows/task.py b/tilebox-workflows/tilebox/workflows/task.py index 9c73e14..0ce86c5 100644 --- a/tilebox-workflows/tilebox/workflows/task.py +++ b/tilebox-workflows/tilebox/workflows/task.py @@ -136,7 +136,8 @@ def _validate_execute_method( f"but got {class_name}.execute{signature}!" ) - if signature.return_annotation is not None and signature.return_annotation != inspect._empty: # noqa: SLF001 + # `from __future__ import annotations` stores `-> None` as the string "None". + if signature.return_annotation not in (None, "None", inspect.Signature.empty): raise TypeError(f"Expected {class_name}.execute{signature} to not have a return value!") return True