{"record":{"id":"011e7d6810409a73","repo":"pathwaycom/pathway","slug":"a-batch-udf-has-to-return-a-list-but-is-annotated","errorCode":null,"errorMessage":"A batch UDF has to return a list but is annotated as returning {sig_return_type}","messagePattern":"A batch UDF has to return a list but is annotated as returning (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"python/pathway/internals/udfs/__init__.py","lineNumber":206,"sourceCode":"            wrapped_sig_return_type is None\n            or (\n                sig_return_type != Any\n                and not dt.dtype_issubclass(\n                    wrapped_sig_return_type, dt.wrap(return_type)\n                )\n            )\n        ):\n            warn(\n                f\"The value of return_type parameter ({return_type}) is inconsistent with\"\n                + f\" UDF's return type annotation ({sig_return_type}).\",\n                stacklevel=3,\n            )\n        if return_type is ...:  # return type only specified in signature\n            if self.max_batch_size is None:\n                return sig_return_type\n            else:\n                if not isinstance(wrapped_sig_return_type, dt.List):\n                    raise ValueError(\n                        f\"A batch UDF has to return a list but is annotated as returning {sig_return_type}\"\n                    )\n                return wrapped_sig_return_type.wrapped\n\n        return return_type\n\n    def _wrap_function(self) -> Callable:\n        func = self.executor._wrap(self.__wrapped__)\n        if self.cache_strategy is not None:\n            func = with_cache_strategy(func, self.cache_strategy)\n        return func\n\n    def _prepare_executor(self, executor: Executor) -> Executor:\n        is_coroutine = inspect.iscoroutinefunction(self.__wrapped__)\n        if is_coroutine and isinstance(executor, SyncExecutor):\n            raise ValueError(\"The function is a coroutine. You can't use SyncExecutor.\")\n        if isinstance(executor, AutoExecutor):\n            return async_executor() if is_coroutine else udfs.sync_executor()","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/pathwaycom/pathway/blob/fa2f74a4649b7c5908690cf60137263d8d80de5f/python/pathway/internals/udfs/__init__.py#L188-L224","documentation":"ValueError raised when a batched UDF (max_batch_size set) relies on its signature for the return type, but the annotation is not a list. For batch mode each argument is a list and the function must return a list, so the signature must be annotated as list[T] (or List[T]); Pathway then unwraps T as the element type.","triggerScenarios":"@pw.udf(max_batch_size=...) def f(x: list[int]) -> int (annotated as returning int, not list[int]); any batched UDF whose -> annotation is a scalar type, Optional, or bare typing construct that does not resolve to dt.List.","commonSituations":"Developer converts a row-wise UDF to batch mode and forgets to change the return annotation from T to list[T]; mixing return_type parameter (which should stay T) with signature annotation conventions.","solutions":["Annotate the batched UDF as returning list[ElementType], e.g. -> list[int]","Alternatively pass return_type=int explicitly (return_type stays the element type even in batch mode)","Ensure the function body actually builds and returns a list of the same length as the inputs"],"exampleFix":"// before\n@pw.udf(max_batch_size=100)\ndef f(x: list[int]) -> int:\n    return [v * 2 for v in x]\n\n// after\n@pw.udf(max_batch_size=100)\ndef f(x: list[int]) -> list[int]:\n    return [v * 2 for v in x]","handlingStrategy":"type-guard","validationCode":"import inspect, typing\n\ndef batch_signature_ok(fn) -> bool:\n    sig = inspect.signature(fn)\n    rt = sig.return_annotation\n    origin = typing.get_origin(rt)\n    return origin in (list, typing.List)","typeGuard":"import typing\n\ndef is_list_annotation(ann) -> bool:\n    return typing.get_origin(ann) in (list, typing.List)","tryCatchPattern":null,"preventionTips":["Annotate batched UDFs as -> list[ElementType]","Keep the element type in return_type= and the full list type in the signature","Add a unit test that constructs every UDF so annotation errors fail fast"],"tags":["pathway","udf","batching","type-annotation"],"backgroundTag":null,"analyzedSha":"fa2f74a4649b7c5908690cf60137263d8d80de5f","analyzedAt":"2026-08-15T01:48:17.006Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}