{"record":{"id":"3f43bff33955c971","repo":"apache/beam","slug":"return-value-not-iterable-s-s","errorCode":null,"errorMessage":"Return value not iterable: %s: %s","messagePattern":"Return value not iterable: (.+?): (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/transforms/core.py","lineNumber":826,"sourceCode":"      if (process_batch_type_hints.output_types\n          != typehints.decorators.IOTypeHints.empty().output_types):\n        if (process_type_hints.output_types\n            != typehints.decorators.IOTypeHints.empty().output_types and\n            process_batch_type_hints.output_types\n            != process_type_hints.output_types):\n          raise TypeError(\n              f\"DoFn {self!r} yields element from both process and \"\n              \"process_batch, but they have mismatched output typehints:\\n\"\n              f\" process: {process_type_hints.output_types}\\n\"\n              f\" process_batch: {process_batch_type_hints.output_types}\")\n\n        process_type_hints = process_type_hints.with_output_types_from(\n            process_batch_type_hints)\n\n    try:\n      process_type_hints = process_type_hints.strip_iterable()\n    except ValueError as e:\n      raise ValueError('Return value not iterable: %s: %s' % (self, e))\n    process_type_hints = process_type_hints.extract_tagged_outputs()\n\n    # Prefer class decorator type hints for backwards compatibility.\n    return get_type_hints(self.__class__).with_defaults(process_type_hints)\n\n  # TODO(sourabhbajaj): Do we want to remove the responsibility of these from\n  # the DoFn or maybe the runner\n  def infer_output_type(self, input_type):\n    # TODO(https://github.com/apache/beam/issues/19824): Side inputs types.\n    return trivial_inference.element_type(\n        _strip_output_annotations(\n            trivial_inference.infer_return_type(self.process, [input_type])))\n\n  @property\n  def _process_defined(self) -> bool:\n    # Check if this DoFn's process method has been overridden\n    # Note that we retrieve the __func__ attribute, if it exists, to get the\n    # underlying function from the bound method.","sourceCodeStart":808,"sourceCodeEnd":844,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/transforms/core.py#L808-L844","documentation":"DoFn.default_type_hints strips the outer iterable from the function's output type because ParDo/FlatMap semantics require the callable's return value to be iterable of elements (core.py:826). If strip_iterable() finds the inferred output type is not iterable, the ValueError is re-raised with the DoFn in the message.","triggerScenarios":"A `process`/`process_batch` method whose annotated or inferred return type is a non-iterable, e.g. annotated `-> int` or `-> str` when Beam expects Iterator/Iterable of elements.","commonSituations":"Using `return` instead of `yield` with a scalar; explicit output annotations like `-> str`; wrappers returning single values instead of generators.","solutions":["Annotate the method with an iterable output type, e.g. -> Iterator[str] or -> Iterable[List[int]].","Use `yield` in `process` so the inferred type is a generator (iterable).","If the method intentionally returns a scalar, wrap it or restructure to yield."],"exampleFix":"# before\nclass MyDoFn(DoFn):\n    def process(self, x) -> str:\n        return str(x)\n\n# after\nclass MyDoFn(DoFn):\n    def process(self, x) -> Iterator[str]:\n        yield str(x)","handlingStrategy":"validation","validationCode":"import inspect\nfrom apache_beam.typehints import typehints\ndef check_iterable_return(fn):\n    ret = inspect.signature(fn).return_annotation\n    if ret is not inspect.Signature.empty and not str(ret).startswith(('Iterator', 'Iterable', 'Generator', 'List', 'list')):\n        raise TypeError(f'{fn.__name__} should have an iterable return annotation, got {ret}')","typeGuard":"def yields_elements(fn) -> bool:\n    import inspect\n    return any('yield' in getattr(c, 'co_code', b'').decode('latin1', 'ignore') for c in inspect.unwrap(fn).__code__.co_consts if hasattr(c, 'co_code'))","tryCatchPattern":"try:\n    hints = dofn.default_type_hints()\nexcept ValueError as e:\n    if 'Return value not iterable' in str(e):\n        logger.error('process must yield/return an iterable: %s', e)\n    raise","preventionTips":["Always use `yield` in process methods rather than `return`","Annotate process/process_batch returns as Iterator[T] or Iterable[T]","Run pipeline type-checking in unit tests (assert_that with output verification)"],"tags":["python","apache-beam","type-hints","valueerror"],"backgroundTag":"type-mismatch","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}