{"record":{"id":"48674db721d226a4","repo":"apache/beam","slug":"missing-language-specification-must-specify-a-language-when","errorCode":null,"errorMessage":"Missing language specification. Must specify a language when using a map with custom logic for %s","messagePattern":"Missing language specification\\. Must specify a language when using a map with custom logic for (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/yaml/yaml_mapping.py","lineNumber":115,"sourceCode":"    return True\n  elif re.fullmatch(r'-?\\d+\\.\\d*', expr):\n    return True\n  elif re.fullmatch(r'\"[^\\\\\"]*\"', expr):\n    return True\n  else:\n    return False\n\n\ndef validate_generic_expression(\n    expr_dict: dict,\n    input_fields: Collection[str],\n    allow_cmp: bool,\n    error_field: str) -> None:\n  if not isinstance(expr_dict, dict):\n    raise ValueError(\n        f\"Ambiguous expression type (perhaps missing quoting?): {expr_dict}\")\n  if len(expr_dict) != 1 or 'expression' not in expr_dict:\n    raise ValueError(\n        \"Missing language specification. \"\n        \"Must specify a language when using a map with custom logic for %s\" %\n        error_field)\n  expr = str(expr_dict['expression'])\n\n  def is_atomic(expr: str):\n    return is_literal(expr) or expr in input_fields\n\n  if is_atomic(expr):\n    return\n\n  if allow_cmp:\n    maybe_cmp = re.fullmatch('(.*)([<>=!]+)(.*)', expr)\n    if maybe_cmp:\n      left, cmp, right = maybe_cmp.groups()\n      if (is_atomic(left.strip()) and is_atomic(right.strip()) and\n          cmp in {'==', '<=', '>=', '<', '>', '!='}):\n        return","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/yaml/yaml_mapping.py#L97-L133","documentation":"When the mapping custom logic IS a dict, Beam YAML requires exactly one key 'expression' plus a language specification for custom map logic. This ValueError fires when the dict has more or fewer than one key, or lacks the 'expression' key, meaning the language (e.g. python/javascript/callable config) cannot be determined.","triggerScenarios":"Calling validate_generic_expression with a dict that is not exactly {expression: <str>} — e.g. {expression: x, extra: y}, {callable: ...}, {} or {language: python} without 'expression'.","commonSituations":"Users combining options like expression and callable in one map; typos like 'expr' instead of 'expression'; forgetting to nest language config correctly in the YAML transform spec.","solutions":["Use exactly one key 'expression' in the map: {expression: \"col + 1\"}.","Remove extra keys from the mapping or move them to the correct config level.","Fix typos so the key is literally 'expression'.","Use 'callable' or 'path'/'name' options instead if you intend non-expression custom logic."],"exampleFix":"# before\nconfiguration:\n  expression: \"x + 1\"\n  language: python   # extra key inside wrong level\n# after\nconfiguration:\n  language: python\n  expression: \"x + 1\"   # language beside expression at spec level","handlingStrategy":"validation","validationCode":"if isinstance(cfg, dict) and (len(cfg) != 1 or 'expression' not in cfg):\n    raise ValueError('use exactly one key: expression')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the expression map single-key; put language at the spec level","Copy documented config examples verbatim","Lint YAML keys against the transform's schema"],"tags":["yaml","beam-yaml","missing-language"],"backgroundTag":"missing-required-config-field","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"}