Skip to content

Graph Class

Unified graph representation supporting various graph types.

This class provides a comprehensive graph representation that can handle: - Simple undirected graphs - Directed graphs - Weighted graphs - Multigraphs (multiple edges between same nodes) - Hypergraphs (edges connecting multiple nodes) - Property graphs (nodes and edges with properties)

The design prioritizes simplicity and extensibility for knowledge representation.

Source code in biocypher/_graph.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
class Graph:
    """Unified graph representation supporting various graph types.

    This class provides a comprehensive graph representation that can handle:
    - Simple undirected graphs
    - Directed graphs
    - Weighted graphs
    - Multigraphs (multiple edges between same nodes)
    - Hypergraphs (edges connecting multiple nodes)
    - Property graphs (nodes and edges with properties)

    The design prioritizes simplicity and extensibility for knowledge representation.
    """

    def __init__(self, name: str = "graph", directed: bool = True):
        """Initialize a new graph.

        Args:
            name: Name of the graph
            directed: Whether the graph is directed (default: True)
        """
        self.name = name
        self.directed = directed

        # Core data structures
        self._nodes: dict[str, Node] = {}
        self._edges: dict[str, Edge] = {}
        self._hyperedges: dict[str, HyperEdge] = {}

        # Indexes for efficient querying
        self._node_types: dict[str, set[str]] = defaultdict(set)
        self._edge_types: dict[str, set[str]] = defaultdict(set)
        self._hyperedge_types: dict[str, set[str]] = defaultdict(set)

        # Adjacency indexes
        self._outgoing: dict[str, set[str]] = defaultdict(set)
        self._incoming: dict[str, set[str]] = defaultdict(set)

        # Statistics
        self._stats = {"nodes": 0, "edges": 0, "hyperedges": 0, "node_types": 0, "edge_types": 0, "hyperedge_types": 0}

    # ==================== NODE OPERATIONS ====================

    def add_node(self, node_id: str, node_type: str, properties: dict[str, Any] | None = None) -> bool:
        """Add a node to the graph.

        Args:
            node_id: Unique identifier for the node
            node_type: Type/category of the node
            properties: Optional properties dictionary

        Returns:
            bool: True if node was added, False if it already exists
        """
        if node_id in self._nodes:
            return False

        node = Node(id=node_id, type=node_type, properties=properties or {})

        self._nodes[node_id] = node
        self._node_types[node_type].add(node_id)
        self._stats["nodes"] += 1
        self._stats["node_types"] = len(self._node_types)

        return True

    def get_node(self, node_id: str) -> Node | None:
        """Get a node by ID.

        Args:
            node_id: Node identifier

        Returns:
            Node object or None if not found
        """
        return self._nodes.get(node_id)

    def has_node(self, node_id: str) -> bool:
        """Check if a node exists.

        Args:
            node_id: Node identifier

        Returns:
            bool: True if node exists
        """
        return node_id in self._nodes

    def remove_node(self, node_id: str) -> bool:
        """Remove a node and all its connected edges.

        Args:
            node_id: Node identifier

        Returns:
            bool: True if node was removed, False if not found
        """
        if node_id not in self._nodes:
            return False

        node = self._nodes[node_id]

        # Remove from type index
        self._node_types[node.type].discard(node_id)
        if not self._node_types[node.type]:
            del self._node_types[node.type]

        # Remove connected edges
        edges_to_remove = []
        for edge_id, edge in self._edges.items():
            if edge.source == node_id or edge.target == node_id:
                edges_to_remove.append(edge_id)

        for edge_id in edges_to_remove:
            self.remove_edge(edge_id)

        # Remove from adjacency indexes
        if node_id in self._outgoing:
            del self._outgoing[node_id]
        if node_id in self._incoming:
            del self._incoming[node_id]

        # Remove node
        del self._nodes[node_id]
        self._stats["nodes"] -= 1
        self._stats["node_types"] = len(self._node_types)

        return True

    def get_nodes(self, node_type: str | None = None) -> list[Node]:
        """Get all nodes, optionally filtered by type.

        Args:
            node_type: Optional filter by node type

        Returns:
            List of Node objects
        """
        if node_type is None:
            return list(self._nodes.values())

        node_ids = self._node_types.get(node_type, set())
        return [self._nodes[node_id] for node_id in node_ids]

    def get_node_ids(self, node_type: str | None = None) -> set[str]:
        """Get all node IDs, optionally filtered by type.

        Args:
            node_type: Optional filter by node type

        Returns:
            Set of node IDs
        """
        if node_type is None:
            return set(self._nodes.keys())

        return self._node_types.get(node_type, set()).copy()

    # ==================== EDGE OPERATIONS ====================

    def add_edge(
        self, edge_id: str, edge_type: str, source: str, target: str, properties: dict[str, Any] | None = None
    ) -> bool:
        """Add an edge to the graph.

        Args:
            edge_id: Unique identifier for the edge
            edge_type: Type/category of the edge
            source: Source node ID
            target: Target node ID
            properties: Optional properties dictionary

        Returns:
            bool: True if edge was added, False if it already exists
        """
        if edge_id in self._edges:
            return False

        # Check if nodes exist
        if source not in self._nodes:
            raise ValueError(f"Source node '{source}' does not exist")
        if target not in self._nodes:
            raise ValueError(f"Target node '{target}' does not exist")

        edge = Edge(id=edge_id, type=edge_type, source=source, target=target, properties=properties or {})

        self._edges[edge_id] = edge
        self._edge_types[edge_type].add(edge_id)

        # Update adjacency indexes
        self._outgoing[source].add(edge_id)
        self._incoming[target].add(edge_id)

        self._stats["edges"] += 1
        self._stats["edge_types"] = len(self._edge_types)

        return True

    def get_edge(self, edge_id: str) -> Edge | None:
        """Get an edge by ID.

        Args:
            edge_id: Edge identifier

        Returns:
            Edge object or None if not found
        """
        return self._edges.get(edge_id)

    def has_edge(self, edge_id: str) -> bool:
        """Check if an edge exists.

        Args:
            edge_id: Edge identifier

        Returns:
            bool: True if edge exists
        """
        return edge_id in self._edges

    def remove_edge(self, edge_id: str) -> bool:
        """Remove an edge from the graph.

        Args:
            edge_id: Edge identifier

        Returns:
            bool: True if edge was removed, False if not found
        """
        if edge_id not in self._edges:
            return False

        edge = self._edges[edge_id]

        # Remove from type index
        self._edge_types[edge.type].discard(edge_id)
        if not self._edge_types[edge.type]:
            del self._edge_types[edge.type]

        # Remove from adjacency indexes
        self._outgoing[edge.source].discard(edge_id)
        self._incoming[edge.target].discard(edge_id)

        # Remove edge
        del self._edges[edge_id]
        self._stats["edges"] -= 1
        self._stats["edge_types"] = len(self._edge_types)

        return True

    def get_edges(self, edge_type: str | None = None) -> list[Edge]:
        """Get all edges, optionally filtered by type.

        Args:
            edge_type: Optional filter by edge type

        Returns:
            List of Edge objects
        """
        if edge_type is None:
            return list(self._edges.values())

        edge_ids = self._edge_types.get(edge_type, set())
        return [self._edges[edge_id] for edge_id in edge_ids]

    def get_edges_between(self, source: str, target: str, edge_type: str | None = None) -> list[Edge]:
        """Get edges between two nodes.

        Args:
            source: Source node ID
            target: Target node ID
            edge_type: Optional filter by edge type

        Returns:
            List of Edge objects
        """
        edges = []
        source_edges = self._outgoing.get(source, set())

        for edge_id in source_edges:
            edge = self._edges[edge_id]
            if edge.target == target and (edge_type is None or edge.type == edge_type):
                edges.append(edge)

        return edges

    # ==================== HYPEREDGE OPERATIONS ====================

    def add_hyperedge(
        self, hyperedge_id: str, hyperedge_type: str, nodes: set[str], properties: dict[str, Any] | None = None
    ) -> bool:
        """Add a hyperedge to the graph.

        Args:
            hyperedge_id: Unique identifier for the hyperedge
            hyperedge_type: Type/category of the hyperedge
            nodes: Set of node IDs to connect
            properties: Optional properties dictionary

        Returns:
            bool: True if hyperedge was added, False if it already exists
        """
        if hyperedge_id in self._hyperedges:
            return False

        # Check if all nodes exist
        for node_id in nodes:
            if node_id not in self._nodes:
                raise ValueError(f"Node '{node_id}' does not exist")

        if len(nodes) < 2:
            raise ValueError("Hyperedge must connect at least 2 nodes")

        hyperedge = HyperEdge(id=hyperedge_id, type=hyperedge_type, nodes=nodes, properties=properties or {})

        self._hyperedges[hyperedge_id] = hyperedge
        self._hyperedge_types[hyperedge_type].add(hyperedge_id)

        self._stats["hyperedges"] += 1
        self._stats["hyperedge_types"] = len(self._hyperedge_types)

        return True

    def get_hyperedge(self, hyperedge_id: str) -> HyperEdge | None:
        """Get a hyperedge by ID.

        Args:
            hyperedge_id: Hyperedge identifier

        Returns:
            HyperEdge object or None if not found
        """
        return self._hyperedges.get(hyperedge_id)

    def has_hyperedge(self, hyperedge_id: str) -> bool:
        """Check if a hyperedge exists.

        Args:
            hyperedge_id: Hyperedge identifier

        Returns:
            bool: True if hyperedge exists
        """
        return hyperedge_id in self._hyperedges

    def get_hyperedges(self, hyperedge_type: str | None = None) -> list[HyperEdge]:
        """Get all hyperedges, optionally filtered by type.

        Args:
            hyperedge_type: Optional filter by hyperedge type

        Returns:
            List of HyperEdge objects
        """
        if hyperedge_type is None:
            return list(self._hyperedges.values())

        hyperedge_ids = self._hyperedge_types.get(hyperedge_type, set())
        return [self._hyperedges[hyperedge_id] for hyperedge_id in hyperedge_ids]

    # ==================== GRAPH TRAVERSAL ====================
    # These methods are placeholders. I am not sure it is useful to focus on traversal,
    # retrieval, analysis, etc. in this module. May be better to focus on the agentic
    # creation of the graph here, and figure out if graph traversal is needed in the
    # use cases we want to support.

    def get_neighbors(self, node_id: str, direction: str = "both") -> set[str]:
        """Get neighboring nodes.

        Args:
            node_id: Node identifier
            direction: "in", "out", or "both"

        Returns:
            Set of neighboring node IDs
        """
        if node_id not in self._nodes:
            return set()

        neighbors = set()

        if direction in ["out", "both"]:
            for edge_id in self._outgoing.get(node_id, set()):
                edge = self._edges[edge_id]
                neighbors.add(edge.target)

        if direction in ["in", "both"]:
            for edge_id in self._incoming.get(node_id, set()):
                edge = self._edges[edge_id]
                neighbors.add(edge.source)

        return neighbors

    def get_connected_edges(self, node_id: str, direction: str = "both") -> list[Edge]:
        """Get edges connected to a node.

        Args:
            node_id: Node identifier
            direction: "in", "out", or "both"

        Returns:
            List of connected Edge objects
        """
        edges = []

        if direction in ["out", "both"]:
            for edge_id in self._outgoing.get(node_id, set()):
                edges.append(self._edges[edge_id])

        if direction in ["in", "both"]:
            for edge_id in self._incoming.get(node_id, set()):
                edges.append(self._edges[edge_id])

        return edges

    def find_paths(self, source: str, target: str, max_length: int = 3) -> list[list[Edge]]:
        """Find all paths between two nodes.

        Args:
            source: Source node ID
            target: Target node ID
            max_length: Maximum path length

        Returns:
            List of paths, each path is a list of Edge objects
        """
        if source not in self._nodes or target not in self._nodes:
            return []

        paths = []
        queue = deque([([], source)])
        visited = set()

        while queue:
            path, current = queue.popleft()

            if current == target and path:
                paths.append(path)
                continue

            if len(path) >= max_length:
                continue

            state = (current, len(path))
            if state in visited:
                continue
            visited.add(state)

            # Explore outgoing edges
            for edge_id in self._outgoing.get(current, set()):
                edge = self._edges[edge_id]
                new_path = path + [edge]
                queue.append((new_path, edge.target))

        return paths

    # ==================== GRAPH ANALYSIS ====================
    # These methods are placeholders. Similar to the traversal methods, not fully clear if
    # these are needed.

    def get_statistics(self) -> dict[str, Any]:
        """Get comprehensive graph statistics.

        Returns:
            Dictionary with graph statistics
        """
        # Ensure we have the latest counts by recalculating from actual data
        actual_nodes = len(self._nodes)
        actual_edges = len(self._edges)
        actual_hyperedges = len(self._hyperedges)

        # Update internal stats to match actual counts
        self._stats["nodes"] = actual_nodes
        self._stats["edges"] = actual_edges
        self._stats["hyperedges"] = actual_hyperedges
        self._stats["node_types"] = len(self._node_types)
        self._stats["edge_types"] = len(self._edge_types)
        self._stats["hyperedge_types"] = len(self._hyperedge_types)

        # Node type distribution
        node_type_dist = {node_type: len(nodes) for node_type, nodes in self._node_types.items()}

        # Edge type distribution
        edge_type_dist = {edge_type: len(edges) for edge_type, edges in self._edge_types.items()}

        # Hyperedge type distribution
        hyperedge_type_dist = {
            hyperedge_type: len(hyperedges) for hyperedge_type, hyperedges in self._hyperedge_types.items()
        }

        # Connectivity analysis
        isolated_nodes = 0
        for node_id in self._nodes:
            if not self.get_neighbors(node_id):
                isolated_nodes += 1

        return {
            "basic": self._stats.copy(),
            "node_types": node_type_dist,
            "edge_types": edge_type_dist,
            "hyperedge_types": hyperedge_type_dist,
            "connectivity": {"isolated_nodes": isolated_nodes, "connected_nodes": actual_nodes - isolated_nodes},
        }

    def get_subgraph(self, node_ids: set[str], include_edges: bool = True) -> "Graph":
        """Extract a subgraph containing specified nodes.

        Args:
            node_ids: Set of node IDs to include
            include_edges: Whether to include edges between included nodes

        Returns:
            New Graph object containing the subgraph
        """
        subgraph = Graph(name=f"{self.name}_subgraph", directed=self.directed)

        # Add nodes
        for node_id in node_ids:
            if node_id in self._nodes:
                node = self._nodes[node_id]
                subgraph.add_node(node.id, node.type, node.properties)

        # Add edges (if requested)
        if include_edges:
            for edge in self._edges.values():
                if edge.source in node_ids and edge.target in node_ids:
                    subgraph.add_edge(edge.id, edge.type, edge.source, edge.target, edge.properties)

        return subgraph

    # ==================== SERIALIZATION ====================
    # Placeholder methods, as serialisation should probably be handled by the corresponding
    # legacy BioCypher modules.

    def to_dict(self) -> dict[str, Any]:
        """Convert graph to dictionary representation.

        Returns:
            Dictionary representation of the graph
        """
        return {
            "name": self.name,
            "directed": self.directed,
            "nodes": [node.to_dict() for node in self._nodes.values()],
            "edges": [edge.to_dict() for edge in self._edges.values()],
            "hyperedges": [hyperedge.to_dict() for hyperedge in self._hyperedges.values()],
            "statistics": self.get_statistics(),
        }

    def to_json(self) -> str:
        """Convert graph to JSON string.

        Returns:
            JSON string representation of the graph
        """
        return json.dumps(self.to_dict(), indent=2)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Graph":
        """Create graph from dictionary representation.

        Args:
            data: Dictionary representation of the graph

        Returns:
            Graph object
        """
        graph = cls(name=data["name"], directed=data["directed"])

        # Add nodes
        for node_data in data["nodes"]:
            node = Node.from_dict(node_data)
            graph._nodes[node.id] = node
            graph._node_types[node.type].add(node.id)

        # Add edges
        for edge_data in data["edges"]:
            edge = Edge.from_dict(edge_data)
            graph._edges[edge.id] = edge
            graph._edge_types[edge.type].add(edge.id)
            graph._outgoing[edge.source].add(edge.id)
            graph._incoming[edge.target].add(edge.id)

        # Add hyperedges
        for hyperedge_data in data["hyperedges"]:
            hyperedge = HyperEdge.from_dict(hyperedge_data)
            graph._hyperedges[hyperedge.id] = hyperedge
            graph._hyperedge_types[hyperedge.type].add(hyperedge.id)

        # Update statistics
        graph._stats["nodes"] = len(graph._nodes)
        graph._stats["edges"] = len(graph._edges)
        graph._stats["hyperedges"] = len(graph._hyperedges)
        graph._stats["node_types"] = len(graph._node_types)
        graph._stats["edge_types"] = len(graph._edge_types)
        graph._stats["hyperedge_types"] = len(graph._hyperedge_types)

        return graph

    @classmethod
    def from_json_string(cls, json_str: str) -> "Graph":
        """Create graph from JSON string.

        Args:
            json_str: JSON string representation of the graph

        Returns:
            Graph object
        """
        data = json.loads(json_str)
        return cls.from_dict(data)

    def from_json(self, json_str: str) -> None:
        """Load graph data from JSON string into this graph instance.

        This method clears the existing graph and loads new data from JSON.

        Args:
            json_str: JSON string representation of the graph
        """
        data = json.loads(json_str)

        # Clear existing data
        self.clear()

        # Update graph properties
        self.name = data["name"]
        self.directed = data["directed"]

        # Add nodes
        for node_data in data["nodes"]:
            node = Node.from_dict(node_data)
            self._nodes[node.id] = node
            self._node_types[node.type].add(node.id)

        # Add edges
        for edge_data in data["edges"]:
            edge = Edge.from_dict(edge_data)
            self._edges[edge.id] = edge
            self._edge_types[edge.type].add(edge.id)
            self._outgoing[edge.source].add(edge.id)
            self._incoming[edge.target].add(edge.id)

        # Add hyperedges
        for hyperedge_data in data["hyperedges"]:
            hyperedge = HyperEdge.from_dict(hyperedge_data)
            self._hyperedges[hyperedge.id] = hyperedge
            self._hyperedge_types[hyperedge.type].add(hyperedge.id)

        # Update statistics
        self._stats["nodes"] = len(self._nodes)
        self._stats["edges"] = len(self._edges)
        self._stats["hyperedges"] = len(self._hyperedges)
        self._stats["node_types"] = len(self._node_types)
        self._stats["edge_types"] = len(self._edge_types)
        self._stats["hyperedge_types"] = len(self._hyperedge_types)

    # ==================== UTILITY METHODS ====================

    def clear(self) -> None:
        """Clear all nodes, edges, and hyperedges from the graph."""
        self._nodes.clear()
        self._edges.clear()
        self._hyperedges.clear()
        self._node_types.clear()
        self._edge_types.clear()
        self._hyperedge_types.clear()
        self._outgoing.clear()
        self._incoming.clear()
        self._stats = {"nodes": 0, "edges": 0, "hyperedges": 0, "node_types": 0, "edge_types": 0, "hyperedge_types": 0}

    def __len__(self) -> int:
        """Return the number of nodes in the graph."""
        return len(self._nodes)

    def __contains__(self, node_id: str) -> bool:
        """Check if a node exists in the graph."""
        return node_id in self._nodes

    def __iter__(self) -> Iterator[Node]:
        """Iterate over all nodes in the graph."""
        return iter(self._nodes.values())

    def __str__(self) -> str:
        """String representation of the graph."""
        stats = self.get_statistics()
        return (
            f"Graph(name='{self.name}', nodes={stats['basic']['nodes']}, "
            f"edges={stats['basic']['edges']}, hyperedges={stats['basic']['hyperedges']})"
        )

    def __repr__(self) -> str:
        return self.__str__()

__contains__(node_id)

Check if a node exists in the graph.

Source code in biocypher/_graph.py
def __contains__(self, node_id: str) -> bool:
    """Check if a node exists in the graph."""
    return node_id in self._nodes

__init__(name='graph', directed=True)

Initialize a new graph.

Parameters:

Name Type Description Default
name str

Name of the graph

'graph'
directed bool

Whether the graph is directed (default: True)

True
Source code in biocypher/_graph.py
def __init__(self, name: str = "graph", directed: bool = True):
    """Initialize a new graph.

    Args:
        name: Name of the graph
        directed: Whether the graph is directed (default: True)
    """
    self.name = name
    self.directed = directed

    # Core data structures
    self._nodes: dict[str, Node] = {}
    self._edges: dict[str, Edge] = {}
    self._hyperedges: dict[str, HyperEdge] = {}

    # Indexes for efficient querying
    self._node_types: dict[str, set[str]] = defaultdict(set)
    self._edge_types: dict[str, set[str]] = defaultdict(set)
    self._hyperedge_types: dict[str, set[str]] = defaultdict(set)

    # Adjacency indexes
    self._outgoing: dict[str, set[str]] = defaultdict(set)
    self._incoming: dict[str, set[str]] = defaultdict(set)

    # Statistics
    self._stats = {"nodes": 0, "edges": 0, "hyperedges": 0, "node_types": 0, "edge_types": 0, "hyperedge_types": 0}

__iter__()

Iterate over all nodes in the graph.

Source code in biocypher/_graph.py
def __iter__(self) -> Iterator[Node]:
    """Iterate over all nodes in the graph."""
    return iter(self._nodes.values())

__len__()

Return the number of nodes in the graph.

Source code in biocypher/_graph.py
def __len__(self) -> int:
    """Return the number of nodes in the graph."""
    return len(self._nodes)

__str__()

String representation of the graph.

Source code in biocypher/_graph.py
def __str__(self) -> str:
    """String representation of the graph."""
    stats = self.get_statistics()
    return (
        f"Graph(name='{self.name}', nodes={stats['basic']['nodes']}, "
        f"edges={stats['basic']['edges']}, hyperedges={stats['basic']['hyperedges']})"
    )

add_edge(edge_id, edge_type, source, target, properties=None)

Add an edge to the graph.

Parameters:

Name Type Description Default
edge_id str

Unique identifier for the edge

required
edge_type str

Type/category of the edge

required
source str

Source node ID

required
target str

Target node ID

required
properties dict[str, Any] | None

Optional properties dictionary

None

Returns:

Name Type Description
bool bool

True if edge was added, False if it already exists

Source code in biocypher/_graph.py
def add_edge(
    self, edge_id: str, edge_type: str, source: str, target: str, properties: dict[str, Any] | None = None
) -> bool:
    """Add an edge to the graph.

    Args:
        edge_id: Unique identifier for the edge
        edge_type: Type/category of the edge
        source: Source node ID
        target: Target node ID
        properties: Optional properties dictionary

    Returns:
        bool: True if edge was added, False if it already exists
    """
    if edge_id in self._edges:
        return False

    # Check if nodes exist
    if source not in self._nodes:
        raise ValueError(f"Source node '{source}' does not exist")
    if target not in self._nodes:
        raise ValueError(f"Target node '{target}' does not exist")

    edge = Edge(id=edge_id, type=edge_type, source=source, target=target, properties=properties or {})

    self._edges[edge_id] = edge
    self._edge_types[edge_type].add(edge_id)

    # Update adjacency indexes
    self._outgoing[source].add(edge_id)
    self._incoming[target].add(edge_id)

    self._stats["edges"] += 1
    self._stats["edge_types"] = len(self._edge_types)

    return True

add_hyperedge(hyperedge_id, hyperedge_type, nodes, properties=None)

Add a hyperedge to the graph.

Parameters:

Name Type Description Default
hyperedge_id str

Unique identifier for the hyperedge

required
hyperedge_type str

Type/category of the hyperedge

required
nodes set[str]

Set of node IDs to connect

required
properties dict[str, Any] | None

Optional properties dictionary

None

Returns:

Name Type Description
bool bool

True if hyperedge was added, False if it already exists

Source code in biocypher/_graph.py
def add_hyperedge(
    self, hyperedge_id: str, hyperedge_type: str, nodes: set[str], properties: dict[str, Any] | None = None
) -> bool:
    """Add a hyperedge to the graph.

    Args:
        hyperedge_id: Unique identifier for the hyperedge
        hyperedge_type: Type/category of the hyperedge
        nodes: Set of node IDs to connect
        properties: Optional properties dictionary

    Returns:
        bool: True if hyperedge was added, False if it already exists
    """
    if hyperedge_id in self._hyperedges:
        return False

    # Check if all nodes exist
    for node_id in nodes:
        if node_id not in self._nodes:
            raise ValueError(f"Node '{node_id}' does not exist")

    if len(nodes) < 2:
        raise ValueError("Hyperedge must connect at least 2 nodes")

    hyperedge = HyperEdge(id=hyperedge_id, type=hyperedge_type, nodes=nodes, properties=properties or {})

    self._hyperedges[hyperedge_id] = hyperedge
    self._hyperedge_types[hyperedge_type].add(hyperedge_id)

    self._stats["hyperedges"] += 1
    self._stats["hyperedge_types"] = len(self._hyperedge_types)

    return True

add_node(node_id, node_type, properties=None)

Add a node to the graph.

Parameters:

Name Type Description Default
node_id str

Unique identifier for the node

required
node_type str

Type/category of the node

required
properties dict[str, Any] | None

Optional properties dictionary

None

Returns:

Name Type Description
bool bool

True if node was added, False if it already exists

Source code in biocypher/_graph.py
def add_node(self, node_id: str, node_type: str, properties: dict[str, Any] | None = None) -> bool:
    """Add a node to the graph.

    Args:
        node_id: Unique identifier for the node
        node_type: Type/category of the node
        properties: Optional properties dictionary

    Returns:
        bool: True if node was added, False if it already exists
    """
    if node_id in self._nodes:
        return False

    node = Node(id=node_id, type=node_type, properties=properties or {})

    self._nodes[node_id] = node
    self._node_types[node_type].add(node_id)
    self._stats["nodes"] += 1
    self._stats["node_types"] = len(self._node_types)

    return True

clear()

Clear all nodes, edges, and hyperedges from the graph.

Source code in biocypher/_graph.py
def clear(self) -> None:
    """Clear all nodes, edges, and hyperedges from the graph."""
    self._nodes.clear()
    self._edges.clear()
    self._hyperedges.clear()
    self._node_types.clear()
    self._edge_types.clear()
    self._hyperedge_types.clear()
    self._outgoing.clear()
    self._incoming.clear()
    self._stats = {"nodes": 0, "edges": 0, "hyperedges": 0, "node_types": 0, "edge_types": 0, "hyperedge_types": 0}

find_paths(source, target, max_length=3)

Find all paths between two nodes.

Parameters:

Name Type Description Default
source str

Source node ID

required
target str

Target node ID

required
max_length int

Maximum path length

3

Returns:

Type Description
list[list[Edge]]

List of paths, each path is a list of Edge objects

Source code in biocypher/_graph.py
def find_paths(self, source: str, target: str, max_length: int = 3) -> list[list[Edge]]:
    """Find all paths between two nodes.

    Args:
        source: Source node ID
        target: Target node ID
        max_length: Maximum path length

    Returns:
        List of paths, each path is a list of Edge objects
    """
    if source not in self._nodes or target not in self._nodes:
        return []

    paths = []
    queue = deque([([], source)])
    visited = set()

    while queue:
        path, current = queue.popleft()

        if current == target and path:
            paths.append(path)
            continue

        if len(path) >= max_length:
            continue

        state = (current, len(path))
        if state in visited:
            continue
        visited.add(state)

        # Explore outgoing edges
        for edge_id in self._outgoing.get(current, set()):
            edge = self._edges[edge_id]
            new_path = path + [edge]
            queue.append((new_path, edge.target))

    return paths

from_dict(data) classmethod

Create graph from dictionary representation.

Parameters:

Name Type Description Default
data dict[str, Any]

Dictionary representation of the graph

required

Returns:

Type Description
Graph

Graph object

Source code in biocypher/_graph.py
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "Graph":
    """Create graph from dictionary representation.

    Args:
        data: Dictionary representation of the graph

    Returns:
        Graph object
    """
    graph = cls(name=data["name"], directed=data["directed"])

    # Add nodes
    for node_data in data["nodes"]:
        node = Node.from_dict(node_data)
        graph._nodes[node.id] = node
        graph._node_types[node.type].add(node.id)

    # Add edges
    for edge_data in data["edges"]:
        edge = Edge.from_dict(edge_data)
        graph._edges[edge.id] = edge
        graph._edge_types[edge.type].add(edge.id)
        graph._outgoing[edge.source].add(edge.id)
        graph._incoming[edge.target].add(edge.id)

    # Add hyperedges
    for hyperedge_data in data["hyperedges"]:
        hyperedge = HyperEdge.from_dict(hyperedge_data)
        graph._hyperedges[hyperedge.id] = hyperedge
        graph._hyperedge_types[hyperedge.type].add(hyperedge.id)

    # Update statistics
    graph._stats["nodes"] = len(graph._nodes)
    graph._stats["edges"] = len(graph._edges)
    graph._stats["hyperedges"] = len(graph._hyperedges)
    graph._stats["node_types"] = len(graph._node_types)
    graph._stats["edge_types"] = len(graph._edge_types)
    graph._stats["hyperedge_types"] = len(graph._hyperedge_types)

    return graph

from_json(json_str)

Load graph data from JSON string into this graph instance.

This method clears the existing graph and loads new data from JSON.

Parameters:

Name Type Description Default
json_str str

JSON string representation of the graph

required
Source code in biocypher/_graph.py
def from_json(self, json_str: str) -> None:
    """Load graph data from JSON string into this graph instance.

    This method clears the existing graph and loads new data from JSON.

    Args:
        json_str: JSON string representation of the graph
    """
    data = json.loads(json_str)

    # Clear existing data
    self.clear()

    # Update graph properties
    self.name = data["name"]
    self.directed = data["directed"]

    # Add nodes
    for node_data in data["nodes"]:
        node = Node.from_dict(node_data)
        self._nodes[node.id] = node
        self._node_types[node.type].add(node.id)

    # Add edges
    for edge_data in data["edges"]:
        edge = Edge.from_dict(edge_data)
        self._edges[edge.id] = edge
        self._edge_types[edge.type].add(edge.id)
        self._outgoing[edge.source].add(edge.id)
        self._incoming[edge.target].add(edge.id)

    # Add hyperedges
    for hyperedge_data in data["hyperedges"]:
        hyperedge = HyperEdge.from_dict(hyperedge_data)
        self._hyperedges[hyperedge.id] = hyperedge
        self._hyperedge_types[hyperedge.type].add(hyperedge.id)

    # Update statistics
    self._stats["nodes"] = len(self._nodes)
    self._stats["edges"] = len(self._edges)
    self._stats["hyperedges"] = len(self._hyperedges)
    self._stats["node_types"] = len(self._node_types)
    self._stats["edge_types"] = len(self._edge_types)
    self._stats["hyperedge_types"] = len(self._hyperedge_types)

from_json_string(json_str) classmethod

Create graph from JSON string.

Parameters:

Name Type Description Default
json_str str

JSON string representation of the graph

required

Returns:

Type Description
Graph

Graph object

Source code in biocypher/_graph.py
@classmethod
def from_json_string(cls, json_str: str) -> "Graph":
    """Create graph from JSON string.

    Args:
        json_str: JSON string representation of the graph

    Returns:
        Graph object
    """
    data = json.loads(json_str)
    return cls.from_dict(data)

get_connected_edges(node_id, direction='both')

Get edges connected to a node.

Parameters:

Name Type Description Default
node_id str

Node identifier

required
direction str

"in", "out", or "both"

'both'

Returns:

Type Description
list[Edge]

List of connected Edge objects

Source code in biocypher/_graph.py
def get_connected_edges(self, node_id: str, direction: str = "both") -> list[Edge]:
    """Get edges connected to a node.

    Args:
        node_id: Node identifier
        direction: "in", "out", or "both"

    Returns:
        List of connected Edge objects
    """
    edges = []

    if direction in ["out", "both"]:
        for edge_id in self._outgoing.get(node_id, set()):
            edges.append(self._edges[edge_id])

    if direction in ["in", "both"]:
        for edge_id in self._incoming.get(node_id, set()):
            edges.append(self._edges[edge_id])

    return edges

get_edge(edge_id)

Get an edge by ID.

Parameters:

Name Type Description Default
edge_id str

Edge identifier

required

Returns:

Type Description
Edge | None

Edge object or None if not found

Source code in biocypher/_graph.py
def get_edge(self, edge_id: str) -> Edge | None:
    """Get an edge by ID.

    Args:
        edge_id: Edge identifier

    Returns:
        Edge object or None if not found
    """
    return self._edges.get(edge_id)

get_edges(edge_type=None)

Get all edges, optionally filtered by type.

Parameters:

Name Type Description Default
edge_type str | None

Optional filter by edge type

None

Returns:

Type Description
list[Edge]

List of Edge objects

Source code in biocypher/_graph.py
def get_edges(self, edge_type: str | None = None) -> list[Edge]:
    """Get all edges, optionally filtered by type.

    Args:
        edge_type: Optional filter by edge type

    Returns:
        List of Edge objects
    """
    if edge_type is None:
        return list(self._edges.values())

    edge_ids = self._edge_types.get(edge_type, set())
    return [self._edges[edge_id] for edge_id in edge_ids]

get_edges_between(source, target, edge_type=None)

Get edges between two nodes.

Parameters:

Name Type Description Default
source str

Source node ID

required
target str

Target node ID

required
edge_type str | None

Optional filter by edge type

None

Returns:

Type Description
list[Edge]

List of Edge objects

Source code in biocypher/_graph.py
def get_edges_between(self, source: str, target: str, edge_type: str | None = None) -> list[Edge]:
    """Get edges between two nodes.

    Args:
        source: Source node ID
        target: Target node ID
        edge_type: Optional filter by edge type

    Returns:
        List of Edge objects
    """
    edges = []
    source_edges = self._outgoing.get(source, set())

    for edge_id in source_edges:
        edge = self._edges[edge_id]
        if edge.target == target and (edge_type is None or edge.type == edge_type):
            edges.append(edge)

    return edges

get_hyperedge(hyperedge_id)

Get a hyperedge by ID.

Parameters:

Name Type Description Default
hyperedge_id str

Hyperedge identifier

required

Returns:

Type Description
HyperEdge | None

HyperEdge object or None if not found

Source code in biocypher/_graph.py
def get_hyperedge(self, hyperedge_id: str) -> HyperEdge | None:
    """Get a hyperedge by ID.

    Args:
        hyperedge_id: Hyperedge identifier

    Returns:
        HyperEdge object or None if not found
    """
    return self._hyperedges.get(hyperedge_id)

get_hyperedges(hyperedge_type=None)

Get all hyperedges, optionally filtered by type.

Parameters:

Name Type Description Default
hyperedge_type str | None

Optional filter by hyperedge type

None

Returns:

Type Description
list[HyperEdge]

List of HyperEdge objects

Source code in biocypher/_graph.py
def get_hyperedges(self, hyperedge_type: str | None = None) -> list[HyperEdge]:
    """Get all hyperedges, optionally filtered by type.

    Args:
        hyperedge_type: Optional filter by hyperedge type

    Returns:
        List of HyperEdge objects
    """
    if hyperedge_type is None:
        return list(self._hyperedges.values())

    hyperedge_ids = self._hyperedge_types.get(hyperedge_type, set())
    return [self._hyperedges[hyperedge_id] for hyperedge_id in hyperedge_ids]

get_neighbors(node_id, direction='both')

Get neighboring nodes.

Parameters:

Name Type Description Default
node_id str

Node identifier

required
direction str

"in", "out", or "both"

'both'

Returns:

Type Description
set[str]

Set of neighboring node IDs

Source code in biocypher/_graph.py
def get_neighbors(self, node_id: str, direction: str = "both") -> set[str]:
    """Get neighboring nodes.

    Args:
        node_id: Node identifier
        direction: "in", "out", or "both"

    Returns:
        Set of neighboring node IDs
    """
    if node_id not in self._nodes:
        return set()

    neighbors = set()

    if direction in ["out", "both"]:
        for edge_id in self._outgoing.get(node_id, set()):
            edge = self._edges[edge_id]
            neighbors.add(edge.target)

    if direction in ["in", "both"]:
        for edge_id in self._incoming.get(node_id, set()):
            edge = self._edges[edge_id]
            neighbors.add(edge.source)

    return neighbors

get_node(node_id)

Get a node by ID.

Parameters:

Name Type Description Default
node_id str

Node identifier

required

Returns:

Type Description
Node | None

Node object or None if not found

Source code in biocypher/_graph.py
def get_node(self, node_id: str) -> Node | None:
    """Get a node by ID.

    Args:
        node_id: Node identifier

    Returns:
        Node object or None if not found
    """
    return self._nodes.get(node_id)

get_node_ids(node_type=None)

Get all node IDs, optionally filtered by type.

Parameters:

Name Type Description Default
node_type str | None

Optional filter by node type

None

Returns:

Type Description
set[str]

Set of node IDs

Source code in biocypher/_graph.py
def get_node_ids(self, node_type: str | None = None) -> set[str]:
    """Get all node IDs, optionally filtered by type.

    Args:
        node_type: Optional filter by node type

    Returns:
        Set of node IDs
    """
    if node_type is None:
        return set(self._nodes.keys())

    return self._node_types.get(node_type, set()).copy()

get_nodes(node_type=None)

Get all nodes, optionally filtered by type.

Parameters:

Name Type Description Default
node_type str | None

Optional filter by node type

None

Returns:

Type Description
list[Node]

List of Node objects

Source code in biocypher/_graph.py
def get_nodes(self, node_type: str | None = None) -> list[Node]:
    """Get all nodes, optionally filtered by type.

    Args:
        node_type: Optional filter by node type

    Returns:
        List of Node objects
    """
    if node_type is None:
        return list(self._nodes.values())

    node_ids = self._node_types.get(node_type, set())
    return [self._nodes[node_id] for node_id in node_ids]

get_statistics()

Get comprehensive graph statistics.

Returns:

Type Description
dict[str, Any]

Dictionary with graph statistics

Source code in biocypher/_graph.py
def get_statistics(self) -> dict[str, Any]:
    """Get comprehensive graph statistics.

    Returns:
        Dictionary with graph statistics
    """
    # Ensure we have the latest counts by recalculating from actual data
    actual_nodes = len(self._nodes)
    actual_edges = len(self._edges)
    actual_hyperedges = len(self._hyperedges)

    # Update internal stats to match actual counts
    self._stats["nodes"] = actual_nodes
    self._stats["edges"] = actual_edges
    self._stats["hyperedges"] = actual_hyperedges
    self._stats["node_types"] = len(self._node_types)
    self._stats["edge_types"] = len(self._edge_types)
    self._stats["hyperedge_types"] = len(self._hyperedge_types)

    # Node type distribution
    node_type_dist = {node_type: len(nodes) for node_type, nodes in self._node_types.items()}

    # Edge type distribution
    edge_type_dist = {edge_type: len(edges) for edge_type, edges in self._edge_types.items()}

    # Hyperedge type distribution
    hyperedge_type_dist = {
        hyperedge_type: len(hyperedges) for hyperedge_type, hyperedges in self._hyperedge_types.items()
    }

    # Connectivity analysis
    isolated_nodes = 0
    for node_id in self._nodes:
        if not self.get_neighbors(node_id):
            isolated_nodes += 1

    return {
        "basic": self._stats.copy(),
        "node_types": node_type_dist,
        "edge_types": edge_type_dist,
        "hyperedge_types": hyperedge_type_dist,
        "connectivity": {"isolated_nodes": isolated_nodes, "connected_nodes": actual_nodes - isolated_nodes},
    }

get_subgraph(node_ids, include_edges=True)

Extract a subgraph containing specified nodes.

Parameters:

Name Type Description Default
node_ids set[str]

Set of node IDs to include

required
include_edges bool

Whether to include edges between included nodes

True

Returns:

Type Description
Graph

New Graph object containing the subgraph

Source code in biocypher/_graph.py
def get_subgraph(self, node_ids: set[str], include_edges: bool = True) -> "Graph":
    """Extract a subgraph containing specified nodes.

    Args:
        node_ids: Set of node IDs to include
        include_edges: Whether to include edges between included nodes

    Returns:
        New Graph object containing the subgraph
    """
    subgraph = Graph(name=f"{self.name}_subgraph", directed=self.directed)

    # Add nodes
    for node_id in node_ids:
        if node_id in self._nodes:
            node = self._nodes[node_id]
            subgraph.add_node(node.id, node.type, node.properties)

    # Add edges (if requested)
    if include_edges:
        for edge in self._edges.values():
            if edge.source in node_ids and edge.target in node_ids:
                subgraph.add_edge(edge.id, edge.type, edge.source, edge.target, edge.properties)

    return subgraph

has_edge(edge_id)

Check if an edge exists.

Parameters:

Name Type Description Default
edge_id str

Edge identifier

required

Returns:

Name Type Description
bool bool

True if edge exists

Source code in biocypher/_graph.py
def has_edge(self, edge_id: str) -> bool:
    """Check if an edge exists.

    Args:
        edge_id: Edge identifier

    Returns:
        bool: True if edge exists
    """
    return edge_id in self._edges

has_hyperedge(hyperedge_id)

Check if a hyperedge exists.

Parameters:

Name Type Description Default
hyperedge_id str

Hyperedge identifier

required

Returns:

Name Type Description
bool bool

True if hyperedge exists

Source code in biocypher/_graph.py
def has_hyperedge(self, hyperedge_id: str) -> bool:
    """Check if a hyperedge exists.

    Args:
        hyperedge_id: Hyperedge identifier

    Returns:
        bool: True if hyperedge exists
    """
    return hyperedge_id in self._hyperedges

has_node(node_id)

Check if a node exists.

Parameters:

Name Type Description Default
node_id str

Node identifier

required

Returns:

Name Type Description
bool bool

True if node exists

Source code in biocypher/_graph.py
def has_node(self, node_id: str) -> bool:
    """Check if a node exists.

    Args:
        node_id: Node identifier

    Returns:
        bool: True if node exists
    """
    return node_id in self._nodes

remove_edge(edge_id)

Remove an edge from the graph.

Parameters:

Name Type Description Default
edge_id str

Edge identifier

required

Returns:

Name Type Description
bool bool

True if edge was removed, False if not found

Source code in biocypher/_graph.py
def remove_edge(self, edge_id: str) -> bool:
    """Remove an edge from the graph.

    Args:
        edge_id: Edge identifier

    Returns:
        bool: True if edge was removed, False if not found
    """
    if edge_id not in self._edges:
        return False

    edge = self._edges[edge_id]

    # Remove from type index
    self._edge_types[edge.type].discard(edge_id)
    if not self._edge_types[edge.type]:
        del self._edge_types[edge.type]

    # Remove from adjacency indexes
    self._outgoing[edge.source].discard(edge_id)
    self._incoming[edge.target].discard(edge_id)

    # Remove edge
    del self._edges[edge_id]
    self._stats["edges"] -= 1
    self._stats["edge_types"] = len(self._edge_types)

    return True

remove_node(node_id)

Remove a node and all its connected edges.

Parameters:

Name Type Description Default
node_id str

Node identifier

required

Returns:

Name Type Description
bool bool

True if node was removed, False if not found

Source code in biocypher/_graph.py
def remove_node(self, node_id: str) -> bool:
    """Remove a node and all its connected edges.

    Args:
        node_id: Node identifier

    Returns:
        bool: True if node was removed, False if not found
    """
    if node_id not in self._nodes:
        return False

    node = self._nodes[node_id]

    # Remove from type index
    self._node_types[node.type].discard(node_id)
    if not self._node_types[node.type]:
        del self._node_types[node.type]

    # Remove connected edges
    edges_to_remove = []
    for edge_id, edge in self._edges.items():
        if edge.source == node_id or edge.target == node_id:
            edges_to_remove.append(edge_id)

    for edge_id in edges_to_remove:
        self.remove_edge(edge_id)

    # Remove from adjacency indexes
    if node_id in self._outgoing:
        del self._outgoing[node_id]
    if node_id in self._incoming:
        del self._incoming[node_id]

    # Remove node
    del self._nodes[node_id]
    self._stats["nodes"] -= 1
    self._stats["node_types"] = len(self._node_types)

    return True

to_dict()

Convert graph to dictionary representation.

Returns:

Type Description
dict[str, Any]

Dictionary representation of the graph

Source code in biocypher/_graph.py
def to_dict(self) -> dict[str, Any]:
    """Convert graph to dictionary representation.

    Returns:
        Dictionary representation of the graph
    """
    return {
        "name": self.name,
        "directed": self.directed,
        "nodes": [node.to_dict() for node in self._nodes.values()],
        "edges": [edge.to_dict() for edge in self._edges.values()],
        "hyperedges": [hyperedge.to_dict() for hyperedge in self._hyperedges.values()],
        "statistics": self.get_statistics(),
    }

to_json()

Convert graph to JSON string.

Returns:

Type Description
str

JSON string representation of the graph

Source code in biocypher/_graph.py
def to_json(self) -> str:
    """Convert graph to JSON string.

    Returns:
        JSON string representation of the graph
    """
    return json.dumps(self.to_dict(), indent=2)

Node

Represents a node in the graph.

Source code in biocypher/_graph.py
@dataclass
class Node:
    """Represents a node in the graph."""

    id: str
    type: str
    properties: dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        if not isinstance(self.id, str):
            raise ValueError("Node ID must be a string")
        if not isinstance(self.type, str):
            raise ValueError("Node type must be a string")

    def to_dict(self) -> dict[str, Any]:
        """Convert node to dictionary representation."""
        return {"id": self.id, "type": self.type, "properties": self.properties}

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Node":
        """Create node from dictionary representation."""
        return cls(id=data["id"], type=data["type"], properties=data.get("properties", {}))

from_dict(data) classmethod

Create node from dictionary representation.

Source code in biocypher/_graph.py
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "Node":
    """Create node from dictionary representation."""
    return cls(id=data["id"], type=data["type"], properties=data.get("properties", {}))

to_dict()

Convert node to dictionary representation.

Source code in biocypher/_graph.py
def to_dict(self) -> dict[str, Any]:
    """Convert node to dictionary representation."""
    return {"id": self.id, "type": self.type, "properties": self.properties}

Edge

Represents an edge in the graph.

Source code in biocypher/_graph.py
@dataclass
class Edge:
    """Represents an edge in the graph."""

    id: str
    type: str
    source: str
    target: str
    properties: dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        if not isinstance(self.id, str):
            raise ValueError("Edge ID must be a string")
        if not isinstance(self.type, str):
            raise ValueError("Edge type must be a string")
        if not isinstance(self.source, str):
            raise ValueError("Edge source must be a string")
        if not isinstance(self.target, str):
            raise ValueError("Edge target must be a string")

    def to_dict(self) -> dict[str, Any]:
        """Convert edge to dictionary representation."""
        return {
            "id": self.id,
            "type": self.type,
            "source": self.source,
            "target": self.target,
            "properties": self.properties,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Edge":
        """Create edge from dictionary representation."""
        return cls(
            id=data["id"],
            type=data["type"],
            source=data["source"],
            target=data["target"],
            properties=data.get("properties", {}),
        )

from_dict(data) classmethod

Create edge from dictionary representation.

Source code in biocypher/_graph.py
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "Edge":
    """Create edge from dictionary representation."""
    return cls(
        id=data["id"],
        type=data["type"],
        source=data["source"],
        target=data["target"],
        properties=data.get("properties", {}),
    )

to_dict()

Convert edge to dictionary representation.

Source code in biocypher/_graph.py
def to_dict(self) -> dict[str, Any]:
    """Convert edge to dictionary representation."""
    return {
        "id": self.id,
        "type": self.type,
        "source": self.source,
        "target": self.target,
        "properties": self.properties,
    }

HyperEdge

Represents a hyperedge connecting multiple nodes.

Source code in biocypher/_graph.py
@dataclass
class HyperEdge:
    """Represents a hyperedge connecting multiple nodes."""

    id: str
    type: str
    nodes: set[str]
    properties: dict[str, Any] = field(default_factory=dict)

    def __post_init__(self):
        if not isinstance(self.id, str):
            raise ValueError("HyperEdge ID must be a string")
        if not isinstance(self.type, str):
            raise ValueError("HyperEdge type must be a string")
        if not isinstance(self.nodes, set):
            raise ValueError("HyperEdge nodes must be a set")
        if len(self.nodes) < 2:
            raise ValueError("HyperEdge must connect at least 2 nodes")

    def to_dict(self) -> dict[str, Any]:
        """Convert hyperedge to dictionary representation."""
        return {"id": self.id, "type": self.type, "nodes": list(self.nodes), "properties": self.properties}

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "HyperEdge":
        """Create hyperedge from dictionary representation."""
        return cls(id=data["id"], type=data["type"], nodes=set(data["nodes"]), properties=data.get("properties", {}))

from_dict(data) classmethod

Create hyperedge from dictionary representation.

Source code in biocypher/_graph.py
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "HyperEdge":
    """Create hyperedge from dictionary representation."""
    return cls(id=data["id"], type=data["type"], nodes=set(data["nodes"]), properties=data.get("properties", {}))

to_dict()

Convert hyperedge to dictionary representation.

Source code in biocypher/_graph.py
def to_dict(self) -> dict[str, Any]:
    """Convert hyperedge to dictionary representation."""
    return {"id": self.id, "type": self.type, "nodes": list(self.nodes), "properties": self.properties}