{"record":{"id":"bb4006e94801a581","repo":"apache/beam","slug":"edge-source-and-target-cannot-be-empty","errorCode":null,"errorMessage":"Edge source and target cannot be empty","messagePattern":"Edge source and target cannot be empty","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/apache_beam_jupyterlab_sidepanel/yaml_parse_utils.py","lineNumber":47,"sourceCode":"  id: str\n  label: str\n  type: str = \"\"\n\n  def __post_init__(self):\n    # Ensure ID is not empty\n    if not self.id:\n      raise ValueError(\"Node ID cannot be empty\")\n\n\n@dataclass\nclass EdgeData:\n  source: str\n  target: str\n  label: str = \"\"\n\n  def __post_init__(self):\n    if not self.source or not self.target:\n      raise ValueError(\"Edge source and target cannot be empty\")\n\n\nclass FlowGraph(TypedDict):\n  nodes: list[dict[str, Any]]\n  edges: list[dict[str, Any]]\n\n\n# ======================== Main Function ========================\n\n\ndef parse_beam_yaml(yaml_str: str, isDryRunMode: bool = False) -> str:\n  \"\"\"\n    Parse Beam YAML and convert to flow graph data structure\n    \n    Args:\n        yaml_str: Input YAML string\n        \n    Returns:","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/runners/interactive/extensions/apache-beam-jupyterlab-sidepanel/apache_beam_jupyterlab_sidepanel/yaml_parse_utils.py#L29-L65","documentation":"EdgeData.__post_init__ raises this ValueError when an edge in the SidePanel YAML graph has an empty source or target. Both endpoints are required to connect two nodes in the interactive pipeline graph, so edges lacking either are rejected at construction.","triggerScenarios":"Defining an edge in the YAML with missing/empty 'source' or 'target' keys, or referencing keys that parsed to empty strings, when yaml_parse_utils constructs EdgeData.","commonSituations":"Hand-edited YAML where the source/target line was deleted; templated YAML with unfilled placeholders ('' defaults); YAML with keys whose values are empty due to unexpanded variables; referencing a node id that was renamed so the field becomes empty.","solutions":["Set non-empty source and target on every edge, matching existing node ids.","Validate the YAML: for each edge check source and target exist in the node id set before parsing.","Fix templates/generators that emit empty endpoint fields.","Rename node ids consistently across nodes and edges to avoid dangling/empty references."],"exampleFix":"// before (yaml)\nedges:\n  - source: node-1\n// after\nedges:\n  - source: node-1\n    target: node-2","handlingStrategy":"validation","validationCode":"def validate_edges(nodes, edges):\n    ids = {n['id'] for n in nodes}\n    for i, e in enumerate(edges):\n        assert e.get('source') and e.get('target'), f'edge[{i}] missing endpoint: {e}'\n        assert e['source'] in ids and e['target'] in ids, f'edge[{i}] references unknown node'","typeGuard":"def edge_is_valid(e: dict) -> bool:\n    return bool(isinstance(e, dict) and e.get('source') and e.get('target'))","tryCatchPattern":"try:\n    edge = EdgeData(source=edge_dict['source'], target=edge_dict['target'])\nexcept ValueError as e:\n    raise YamlSchemaError(f'Bad edge in SidePanel YAML: {edge_dict}') from e","preventionTips":["Give every edge non-empty source and target matching existing node ids","Run a pre-parse consistency check of nodes vs edges","Rename node ids in edges whenever nodes are renamed","Fix YAML templates so endpoint placeholders are always filled"],"tags":["python","yaml","validation","jupyterlab","apache-beam"],"backgroundTag":"empty-required-field","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}