{"record":{"id":"7368042f72ac2e08","repo":"apache/beam","slug":"split-result-must-be-a-tuple-that-contains-split-position","errorCode":null,"errorMessage":"Split result must be a tuple that contains split position and split fraction. Received: %r","messagePattern":"Split result must be a tuple that contains split position and split fraction\\. Received: %r","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/io/source_test_utils.py","lineNumber":303,"sourceCode":"    stop_position=None):\n\n  range_tracker = source.get_range_tracker(start_position, stop_position)\n  assert isinstance(range_tracker, iobase.RangeTracker)\n  current_items = []\n  reader = source.read(range_tracker)\n  # Reading 'num_items_to_read_before_split' items.\n  reader_iter = iter(reader)\n  for _ in range(num_items_to_read_before_split):\n    current_items.append(next(reader_iter))\n\n  suggested_split_position = range_tracker.position_at_fraction(split_fraction)\n\n  stop_position_before_split = range_tracker.stop_position()\n  split_result = range_tracker.try_split(suggested_split_position)\n\n  if split_result is not None:\n    if len(split_result) != 2:\n      raise ValueError(\n          'Split result must be a tuple that contains split '\n          'position and split fraction. Received: %r' % (split_result, ))\n\n    if range_tracker.stop_position() != split_result[0]:\n      raise ValueError(\n          'After a successful split, the stop position of the '\n          'RangeTracker must be the same as the returned split '\n          'position. Observed %r and %r which are different.' %\n          (range_tracker.stop_position() % (split_result[0], )))\n\n    if split_fraction < 0 or split_fraction > 1:\n      raise ValueError(\n          'Split fraction must be within the range [0,1]',\n          'Observed split fraction was %r.' % (split_result[1], ))\n\n  stop_position_after_split = range_tracker.stop_position()\n  if split_result and stop_position_after_split == stop_position_before_split:\n    raise ValueError(","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/io/source_test_utils.py#L285-L321","documentation":"Raised inside _assert_split_at_fraction_behavior (used by assert_split_at_fraction_behavior and assert_split_at_fraction_binary) when RangeTracker.try_split() returns a non-None result that is not a 2-element tuple of (split_position, split_fraction). A successful split must return exactly two values per the iobase.RangeTracker contract. This indicates a custom RangeTracker implementation violating the try_split return contract.","triggerScenarios":"A custom RangeTracker's try_split returns None-of-length-2 structures such as a single value, a 3-tuple, a list with extra items, or a non-sized object, while the test harness performs try_split after reading num_items_to_read_before_split items.","commonSituations":"Implementing a custom RangeTracker for a new connector and returning only the new position from try_split, or returning (position, fraction, old_stop).","solutions":["Return a 2-tuple (split_position, split_fraction) from try_split on success.","Return None (not an empty tuple/list) when the split cannot be performed.","Prefer subclassing apache_beam.io.iobase.RangeTracker, which enforces the contract.","Log repr(split_result) (shown in the message) to see the actual returned structure."],"exampleFix":"# before\ndef try_split(self, fraction):\n  return self.position_at_fraction(fraction)\n# after\ndef try_split(self, fraction):\n  pos = self.position_at_fraction(fraction)\n  if pos is None:\n    return None\n  return (pos, fraction)","handlingStrategy":"type-guard","validationCode":"res = tracker.try_split(pos)\nif res is not None and not (isinstance(res, tuple) and len(res) == 2):\n    raise TypeError(\"try_split must return (position, fraction) or None\")","typeGuard":"def is_valid_split_result(res):\n    return res is None or (isinstance(res, tuple) and len(res) == 2)","tryCatchPattern":"try:\n    source_test_utils.assert_split_at_fraction_behavior(src, n, f, outcome)\nexcept ValueError as e:\n    if \"Split result must be a tuple\" in str(e):\n        logger.error(\"bad try_split return: %s\", e)\n    raise","preventionTips":["Subclass iobase.RangeTracker instead of reimplementing the interface","Return exactly (position, fraction) on success and None on refusal","Type-check try_split's return in your connector's own tests"],"tags":["apache-beam","range-tracker","dynamic-splitting","contract-violation"],"backgroundTag":"invalid-argument-format","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"}