{"record":{"id":"d429ec6fba77b0ab","repo":"apache/beam","slug":"secret-spec-must-be-a-string-got-type-spec-name","errorCode":null,"errorMessage":"Secret 'spec' must be a string, got {type(spec).__name__}","messagePattern":"Secret 'spec' must be a string, got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/utils/secret.py","lineNumber":128,"sourceCode":"          f'Invalid secret type {secret_type}, currently only '\n          'GcpSecret and GcpHsmGeneratedSecret are supported')\n\n    return cls.from_json(json.dumps(param_map), secret_manager)\n\n  @classmethod\n  def from_json(\n      cls, spec: str, secret_manager: Optional[str] = None) -> 'Secret':\n    \"\"\"Return a Secret instance based on secret_manager provider and secret specification.\n\n    Args:\n      spec: Secret string (raw secret or JSON specification string).\n      secret_manager: Secret manager string (e.g. 'GoogleCloudSecretManager').\n\n    Returns:\n      An instance of Secret.\n    \"\"\"\n    if not isinstance(spec, str):\n      raise TypeError(\n          f\"Secret 'spec' must be a string, got {type(spec).__name__}\")\n\n    secret_manager_name = (\n        secret_manager.strip()\n        if secret_manager and secret_manager.strip() else None)\n\n    spec_dict = None\n    try:\n      spec_dict = json.loads(spec)\n      if not isinstance(spec_dict, dict):\n        spec_dict = None\n    except Exception:\n      try:\n        import ast\n        spec_dict = ast.literal_eval(spec)\n        if not isinstance(spec_dict, dict):\n          spec_dict = None\n      except Exception:","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/utils/secret.py#L110-L146","documentation":"Secret.from_json builds a Secret from a JSON spec string plus a secret manager name. Because the spec must be parseable JSON text, a non-str spec (dict, bytes, None) raises TypeError naming the actual type received.","triggerScenarios":"Calling from_json with a dict (e.g. {'name': ...}) instead of json.dumps of it, or with None/bytes when wiring parameters through parse_secret_option.","commonSituations":"Passing an already-parsed dict from YAML config; API change where callers used to accept dicts; forgetting json.dumps when constructing the spec.","solutions":["Serialize the spec: Secret.from_json(json.dumps(param_map), secret_manager)","Ensure the value is not None before calling (check upstream parsing)","If you have a dict, use parse_secret_option or json.dumps first"],"exampleFix":"// before\nSecret.from_json({'name': 'my-secret'}, 'GoogleCloudSecretManager')\n// after\nimport json\nSecret.from_json(json.dumps({'name': 'my-secret'}), 'GoogleCloudSecretManager')","handlingStrategy":"type-guard","validationCode":"if not isinstance(spec, str):\n    raise TypeError(f'spec must be a JSON string, got {type(spec).__name__}')","typeGuard":"def is_str_spec(spec):\n    return isinstance(spec, str)","tryCatchPattern":"try:\n    secret = Secret.from_json(spec, secret_manager)\nexcept TypeError as e:\n    logging.error('bad spec type: %s', e)\n    secret = Secret.from_json(json.dumps(spec), secret_manager)  # if spec is a dict","preventionTips":["Always json.dumps dicts before from_json","Check for None specs when plumbing parsed config","Keep the spec as a string end-to-end once parsed"],"tags":["secrets","type-mismatch","json"],"backgroundTag":"type-mismatch","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"}