{"record":{"id":"7889e00488166a5f","repo":"apache/beam","slug":"field-expression-r-at-s-must-be-a-callable-or-a-string","errorCode":null,"errorMessage":"Field expression %r at %s must be a callable or a string.","messagePattern":"Field expression %r at (.+?) must be a callable or a string\\.","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/transforms/core.py","lineNumber":3570,"sourceCode":"    return common_urns.primitives.GROUP_BY_KEY.urn, None\n\n  @staticmethod\n  @PTransform.register_urn(common_urns.primitives.GROUP_BY_KEY.urn, None)\n  def from_runner_api_parameter(\n      unused_ptransform, unused_payload, unused_context):\n    return GroupByKey()\n\n  def runner_api_requires_keyed_input(self):\n    return True\n\n\ndef _expr_to_callable(expr, pos):\n  if isinstance(expr, str):\n    return lambda x: getattr(x, expr)\n  elif callable(expr):\n    return expr\n  else:\n    raise TypeError(\n        'Field expression %r at %s must be a callable or a string.' %\n        (expr, pos))\n\n\nclass GroupBy(PTransform):\n  \"\"\"Groups a PCollection by one or more expressions, used to derive the key.\n\n  `GroupBy(expr)` is roughly equivalent to\n\n      beam.Map(lambda v: (expr(v), v)) | beam.GroupByKey()\n\n  but provides several conveniences, e.g.\n\n      * Several arguments may be provided, as positional or keyword arguments,\n        resulting in a tuple-like key. For example `GroupBy(a=expr1, b=expr2)`\n        groups by a key with attributes `a` and `b` computed by applying\n        `expr1` and `expr2` to each element.\n","sourceCodeStart":3552,"sourceCodeEnd":3588,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/transforms/core.py#L3552-L3588","documentation":"Raised by apache_beam.transforms.core._expr_to_callable, a helper used by GroupBy to turn key/field expressions into callables. A field expression must either be a string naming an attribute of each element, or a callable taking the element and returning the key value. Anything else (int, dict, None, etc.) cannot be interpreted, so a TypeError is raised naming the offending expression and its position.","triggerScenarios":"Passing a non-string, non-callable to GroupBy's field expressions, e.g. beam.GroupBy(42), GroupBy(None), GroupBy({'a': 1}), or a list of expressions where one element is not a str/callable.","commonSituations":"Typos such as GroupBy(a, b) passing raw variable names (values like 42) instead of strings 'a'; refactoring code that changed a lambda into a value; building expressions dynamically from config where a numeric field index is passed instead of a field name string.","solutions":["Pass attribute names as strings: beam.GroupBy('field_name') instead of beam.GroupBy(field_name).","If the key is computed, pass a lambda/function: beam.GroupBy(lambda x: x.field).","For multiple fields, pass multiple strings/callables: beam.GroupBy('a', 'b').","Validate your expression list before constructing: all(isinstance(e, (str,)) or callable(e) for e in exprs)."],"exampleFix":"// before\nimport beam\nresult = pc | beam.GroupBy(field_id)  # field_id = 7\n// after\nresult = pc | beam.GroupBy('field_id')","handlingStrategy":"type-guard","validationCode":"def check_exprs(exprs):\n    assert all(isinstance(e, str) or callable(e) for e in exprs), 'GroupBy expressions must be str or callable'","typeGuard":"def is_field_expr(e):\n    return isinstance(e, str) or callable(e)","tryCatchPattern":null,"preventionTips":["Always quote field names passed to GroupBy","Use lambdas for computed keys","Validate dynamic expression lists before building the transform"],"tags":["python","apache-beam","groupby","argument-validation"],"backgroundTag":"invalid-argument-value","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"}