apache/beam · error · ValueError

Unexpected unbound parameter

Error message

Unexpected unbound parameter: %s

What it means

getcallargs_forhints binds a function's signature to its type hints; parameters with defaults that end up unbound are declared Any. If a parameter is still unbound after that — meaning signature.bind() should have caught it earlier — Beam raises ValueError('Unexpected unbound parameter') as an internal invariant check.

Solutions

  1. Restructure the callable so every parameter is bindable (avoid exotic POSITIONAL_ONLY + VAR_KEYWORD mixes in hinted functions)
  2. If decorating builtins, wrap them in a plain Python function with an explicit signature and hint that
  3. Ensure functools.wraps copies __wrapped__/__signature__ correctly or set __annotations__ explicitly
  4. File/upgrade against apache-beam if a normal Python signature triggers it — this is meant to be unreachable

Example fix

// before
wrapper = functools.wraps(some_builtin)(fn)  # builtin signature confuses binding
// after
def wrapper(*args, **kwargs):  # explicit plain-Python signature with hints
  return some_builtin(*args, **kwargs)
Defensive patterns

Strategy: try-catch

Validate before calling

import inspect
def bindable(fn):
  try:
    inspect.signature(fn).bind(*[inspect.Parameter.empty]*len(inspect.signature(fn).parameters))
  except TypeError:
    return False
  return True

Type guard

def is_plain_python_callable(fn):
  return inspect.isfunction(fn) or inspect.ismethod(fn)

Try / catch

try:
  bound = getcallargs_forhints(fn, hints)
except ValueError as e:
  if 'Unexpected unbound parameter' in str(e):
    bound = None  # skip type checking for exotic signatures

Prevention

When it happens

Trigger: A function whose signature.bind() behavior diverges from Beam's manual binding loop — typically signatures with POSITIONAL_ONLY/VAR_POSITIONAL/KEYWORD_ONLY mixes, functools.wraps of builtins (partial-builtins), or C functions where inspect semantics are unusual.

Common situations: Type-checking decorated builtins or C-extension callables; wrapping functions with functools.partial where the wrapper signature misreports parameters; Beam version regressions around new Python syntax (e.g. positional-only params in 3.8+).

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at sdks/python/apache_beam/typehints/decorators.py:803

        bound_args[param.name] = _normalize_var_positional_hint(
            bound_args[param.name])
      elif param.kind == param.VAR_KEYWORD:
        bound_args[param.name] = _normalize_var_keyword_hint(
            bound_args[param.name], param.name)
    else:
      # Unbound: must have a default or be variadic.
      if param.annotation != param.empty:
        bound_args[param.name] = param.annotation
      elif param.kind == param.VAR_POSITIONAL:
        bound_args[param.name] = _ANY_VAR_POSITIONAL
      elif param.kind == param.VAR_KEYWORD:
        bound_args[param.name] = _ANY_VAR_KEYWORD
      elif param.default is not param.empty:
        # Declare unbound parameters with defaults to be Any.
        bound_args[param.name] = typehints.Any
      else:
        # This case should be caught by signature.bind() above.
        raise ValueError('Unexpected unbound parameter: %s' % param.name)

  return dict(bound_args)


def get_type_hints(fn: Any) -> IOTypeHints:
  """Gets the type hint associated with an arbitrary object fn.

  Always returns a valid IOTypeHints object, creating one if necessary.
  """
  # pylint: disable=protected-access
  if not hasattr(fn, '_type_hints'):
    try:
      fn._type_hints = IOTypeHints.empty()
    except (AttributeError, TypeError):
      # Can't add arbitrary attributes to this object,
      # but might have some restrictions anyways...
      hints = IOTypeHints.empty()
      return hints

View on GitHub (pinned to 12126d8942)