{"record":{"id":"c7e5a4281c17ffd2","repo":"apache/beam","slug":"f-test-specification-must-be-an-object-got-type-test-spec","errorCode":null,"errorMessage":"f'Test specification must be an object, got {type(test_spec)}'","messagePattern":"f'Test specification must be an object, got (.+?)'","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/yaml/yaml_testing.py","lineNumber":142,"sourceCode":"\ndef _preprocess_for_testing(pipeline_spec):\n  spec = yaml_transform.pipeline_as_composite(pipeline_spec['pipeline'])\n  # These are idempotent, so it's OK to do them preemptively.\n  for phase in [\n      yaml_transform.ensure_transforms_have_types,\n      yaml_transform.preprocess_source_sink,\n      yaml_transform.preprocess_chain,\n      yaml_transform.tag_explicit_inputs,\n      yaml_transform.normalize_inputs_outputs,\n  ]:\n    spec = yaml_transform.apply_phase(phase, spec)\n\n  return spec\n\n\ndef validate_test_spec(test_spec):\n  if not isinstance(test_spec, dict):\n    raise TypeError(\n        f'Test specification must be an object, got {type(test_spec)}')\n  identifier = (\n      test_spec.get('name', 'unknown') +\n      f' at line {yaml_transform.SafeLineLoader.get_line(test_spec)}')\n\n  if not isinstance(test_spec.get('allowed_sources', []), list):\n    raise TypeError(\n        f'allowed_sources of test specification {identifier} '\n        f'must be a list, got {type(test_spec[\"allowed_sources\"])}')\n\n  if (not test_spec.get('expected_outputs', []) and\n      not test_spec.get('expected_inputs', [])):\n    raise ValueError(\n        f'test specification {identifier} '\n        f'must have at least one expected_outputs or expected_inputs')\n\n  unknown_attrs = set(\n      yaml_transform.SafeLineLoader.strip_metadata(test_spec).keys()) - set([","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/yaml/yaml_testing.py#L124-L160","documentation":"Raised by validate_test_spec when the object passed to a YAML pipeline test is not a mapping (e.g. it was loaded from malformed YAML into a string or list). The validator cannot inspect test attributes like mock_inputs/expected_outputs on a non-dict, so it rejects the whole test_spec before any field checks run. The input at fault is the test_spec argument itself, typically produced by yaml.safe_load of a test file.","triggerScenarios":"Passing a YAML test entry that is a string or list instead of a mapping with keys like name, expected_outputs, allowed_sources; e.g. 'tests: [\"my_test\"]'.","commonSituations":"Malformed YAML indentation turning the test spec into a string, or quoting the whole test object as a scalar.","solutions":["Make each entry under 'tests:' a mapping with test-spec keys.","Check YAML indentation so the test object's keys nest under the entry.","Validate with isinstance(spec, dict) before calling validate_test_spec."],"exampleFix":"# before\ntests:\n  - just_a_name\n\n# after\ntests:\n  - name: just_a_name\n    expected_outputs:\n      out: [...]","handlingStrategy":"type-guard","validationCode":"if not isinstance(test_spec, dict):\n    raise TypeError(f'test spec must be a mapping, got {type(test_spec).__name__}')","typeGuard":"def is_test_spec(x) -> bool:\n    return isinstance(x, dict) and ('expected_outputs' in x or 'expected_inputs' in x)","tryCatchPattern":"try:\n    validate_test_spec(test_spec)\nexcept TypeError as e:\n    logging.error('Malformed test spec: %s', e)\n    raise","preventionTips":["Ensure each tests: entry is a YAML mapping","Check indentation after editing test YAML","Parse the YAML and inspect types before running tests"],"tags":["python","apache-beam","yaml","testing","type-error"],"backgroundTag":"type-mismatch","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T21:17:11.552Z"}