apache/beam · error · ValueError

Unable to identify callable from %r

Error message

Unable to identify callable from %r

What it means

load_from_script parses a Python source string to find a callable assigned to a variable (or def) to exec. If no assignment/def line is found after scanning all lines, Beam raises ValueError because there is no callable to extract from the source.

Solutions

  1. Wrap the expression in an assignment: 'my_callable = lambda x: x*2'
  2. Or use a def: 'def my_callable(x): return x*2'
  3. If you already have a callable object, do not serialize via source — pass it directly

Example fix

// before
load_from_script('lambda x: x * 2')
// after
load_from_script('double = lambda x: x * 2')
Defensive patterns

Strategy: validation

Validate before calling

def is_valid_callable_source(source: str) -> bool:
    import ast
    try:
        tree = ast.parse(source)
    except SyntaxError:
        return False
    return any(isinstance(n, (ast.Assign, ast.FunctionDef)) for n in tree.body)

Try / catch

try:
    fn = load_from_script(source)
except ValueError as e:
    logging.error('no callable in source: %s', e)
    raise

Prevention

When it happens

Trigger: Calling load_from_script(source) or load_from_source(source) with a string containing no top-level assignment or function definition (e.g. only an expression like 'lambda x: x' with no 'name = ' prefix, or only imports).

Common situations: Passing a lambda expression directly instead of 'fn = lambda x: ...'; passing a fully-qualified callable name instead of source code; whitespace/comment-only strings.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/34b78bc34ef04889. Report an issue: GitHub.

Appendix: source

Thrown at sdks/python/apache_beam/utils/python_callable.py:100

        if line.strip() and line.strip()[0] != '#'
    ]
    common_indent = min(len(line) - len(line.lstrip()) for line in lines)
    lines = [line[common_indent:] for line in lines]

    if method_name is None:
      for ix, line in reversed(list(enumerate(lines))):
        if line[0] != ' ':
          if line.startswith('def '):
            method_name = line[4:line.index('(')].strip()
          elif line.startswith('class '):
            method_name = line[5:line.index('(') if '(' in
                               line else line.index(':')].strip()
          else:
            method_name = '__python_callable__'
            lines[ix] = method_name + ' = ' + line
          break
      else:
        raise ValueError("Unable to identify callable from %r" % source)

    # pylint: disable=exec-used
    # pylint: disable=ungrouped-imports
    import apache_beam as beam
    exec_globals = {'beam': beam}
    exec('\n'.join(lines), exec_globals)
    return exec_globals[method_name]

  def default_label(self):
    src = self._source.strip()
    last_line = src.split('\n')[-1]
    if last_line[0] != ' ' and len(last_line) < 72:
      return last_line
    # Avoid circular import.
    from apache_beam.transforms.ptransform import label_from_callable
    return label_from_callable(self._callable)

  @property

View on GitHub (pinned to 12126d8942)