{"record":{"id":"97e9992350ee4a51","repo":"apache/beam","slug":"unexpected-unbound-parameter-s","errorCode":null,"errorMessage":"Unexpected unbound parameter: %s","messagePattern":"Unexpected unbound parameter: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/typehints/decorators.py","lineNumber":803,"sourceCode":"        bound_args[param.name] = _normalize_var_positional_hint(\n            bound_args[param.name])\n      elif param.kind == param.VAR_KEYWORD:\n        bound_args[param.name] = _normalize_var_keyword_hint(\n            bound_args[param.name], param.name)\n    else:\n      # Unbound: must have a default or be variadic.\n      if param.annotation != param.empty:\n        bound_args[param.name] = param.annotation\n      elif param.kind == param.VAR_POSITIONAL:\n        bound_args[param.name] = _ANY_VAR_POSITIONAL\n      elif param.kind == param.VAR_KEYWORD:\n        bound_args[param.name] = _ANY_VAR_KEYWORD\n      elif param.default is not param.empty:\n        # Declare unbound parameters with defaults to be Any.\n        bound_args[param.name] = typehints.Any\n      else:\n        # This case should be caught by signature.bind() above.\n        raise ValueError('Unexpected unbound parameter: %s' % param.name)\n\n  return dict(bound_args)\n\n\ndef get_type_hints(fn: Any) -> IOTypeHints:\n  \"\"\"Gets the type hint associated with an arbitrary object fn.\n\n  Always returns a valid IOTypeHints object, creating one if necessary.\n  \"\"\"\n  # pylint: disable=protected-access\n  if not hasattr(fn, '_type_hints'):\n    try:\n      fn._type_hints = IOTypeHints.empty()\n    except (AttributeError, TypeError):\n      # Can't add arbitrary attributes to this object,\n      # but might have some restrictions anyways...\n      hints = IOTypeHints.empty()\n      return hints","sourceCodeStart":785,"sourceCodeEnd":821,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/typehints/decorators.py#L785-L821","documentation":"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.","triggerScenarios":"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.","commonSituations":"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+).","solutions":["Restructure the callable so every parameter is bindable (avoid exotic POSITIONAL_ONLY + VAR_KEYWORD mixes in hinted functions)","If decorating builtins, wrap them in a plain Python function with an explicit signature and hint that","Ensure functools.wraps copies __wrapped__/__signature__ correctly or set __annotations__ explicitly","File/upgrade against apache-beam if a normal Python signature triggers it — this is meant to be unreachable"],"exampleFix":"// before\nwrapper = functools.wraps(some_builtin)(fn)  # builtin signature confuses binding\n// after\ndef wrapper(*args, **kwargs):  # explicit plain-Python signature with hints\n  return some_builtin(*args, **kwargs)","handlingStrategy":"try-catch","validationCode":"import inspect\ndef bindable(fn):\n  try:\n    inspect.signature(fn).bind(*[inspect.Parameter.empty]*len(inspect.signature(fn).parameters))\n  except TypeError:\n    return False\n  return True","typeGuard":"def is_plain_python_callable(fn):\n  return inspect.isfunction(fn) or inspect.ismethod(fn)","tryCatchPattern":"try:\n  bound = getcallargs_forhints(fn, hints)\nexcept ValueError as e:\n  if 'Unexpected unbound parameter' in str(e):\n    bound = None  # skip type checking for exotic signatures","preventionTips":["Wrap builtins/C functions in plain Python wrappers before hinting","Keep hinted signatures simple (no exotic positional-only + **kwargs mixes)","Set __annotations__ explicitly on decorators"],"tags":["python","type-hints","apache-beam","integrity"],"backgroundTag":"internal-invariant-violation","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}