# How class_ingest/relationships.py Extracts Class Hierarchies and Inheritance in code-graph-rag

> Learn how class_ingest/relationships.py in code-graph-rag extracts class hierarchies and inheritance. Discover its process for parent class discovery and graph edge creation.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: how-to-guide
- Published: 2026-08-16

---

**The [`class_ingest/relationships.py`](https://github.com/vitali87/code-graph-rag/blob/main/class_ingest/relationships.py) module in the vitali87/code-graph-rag repository orchestrates inheritance extraction by delegating parent class discovery to language-specific helpers, storing results in a mutable mapping, and emitting `INHERITS` or `IMPLEMENTS` graph edges with support for deferred resolution of cross-file dependencies.**

The [`relationships.py`](https://github.com/vitali87/code-graph-rag/blob/main/relationships.py) file sits at the core of the *class-ingest* pipeline in **code-graph-rag**, a multi-language code analysis tool that builds queryable knowledge graphs from source code. When tree-sitter parses a class or enum declaration, this module translates syntactic inheritance constructs into persistent graph relationships that power downstream analysis like method override detection and architectural queries.

## Overview of the Inheritance Extraction Pipeline

The extraction process follows three coordinated phases: **parent discovery**, **mapping storage**, and **edge emission**. Each phase handles language-specific nuances while maintaining a unified graph schema across C++, C#, Java, Python, TypeScript/JavaScript, PHP, Rust, Dart, and other supported languages.

### Phase 1: Identify Parent Classes via Delegation

The entry point `create_class_relationships` does not parse AST nodes directly. Instead, it delegates to **`extract_parent_classes`** from [`parent_extraction.py`](https://github.com/vitali87/code-graph-rag/blob/main/parent_extraction.py), which contains language-specific logic for walking inheritance syntax:

- **C#**: `split_csharp_bases` separates the single base class from implemented interfaces
- **Java**: `extract_java_superclass` handles `extends` clauses
- **Python**: `extract_python_superclasses` resolves generic bases and import aliases
- **TypeScript/JavaScript**: `extract_js_ts_heritage_parents` processes `extends`, mixins, and `implements`
- **C++**: `extract_cpp_parent_bases` (called directly for `CPP_CLASS_TYPES`) captures base specifiers that may reference headers not yet parsed

This delegation pattern keeps [`relationships.py`](https://github.com/vitali87/code-graph-rag/blob/main/relationships.py) focused on graph construction while [`parent_extraction.py`](https://github.com/vitali87/code-graph-rag/blob/main/parent_extraction.py) evolves independently with new language support.

### Phase 2: Record Inheritance Mappings for Incremental Rebuilds

Parent class lists are stored in a mutable dictionary passed through the call chain:

```python
class_inheritance[class_qn] = parent_classes

```

This mapping at **line 44 of [`relationships.py`](https://github.com/vitali87/code-graph-rag/blob/main/relationships.py)** enables incremental ingestion workflows. When re-indexing changed files, the system can rebuild affected portions of the class hierarchy without reparsing the entire codebase.

### Phase 3: Emit Relationship Edges with Deferred Resolution

The core complexity lies in deciding **when** to create edges. The implementation at **lines 57-133** branches on two factors: the target language and whether deferral is explicitly requested.

#### C++ Cross-File Deferral

C++ base classes frequently reside in separate headers. Rather than fail or emit incomplete edges, the code creates **`DeferredCppInherit`** records at **lines 65-75**:

```python
DeferredCppInherit(
    written_name=base_name,
    guess_qn=guessed_qualified_name,
    namespace_path=current_namespace
)

```

A later pass (`resolve_deferred_cpp_inherits`) resolves these against the complete symbol table once all headers are registered.

#### Immediate Edge Creation for Other Languages

For non-C++ languages where parent symbols are resolvable in the current translation unit, `create_inheritance_relationship` emits edges directly at **lines 92-101**:

```python
ingestor.ensure_relationship_batch(
    (child_node_type, cs.KEY_QUALIFIED_NAME, child_qn),
    cs.RelationshipType.INHERITS,
    (parent_type, cs.KEY_QUALIFIED_NAME, parent_qn),
    {cs.KEY_BASE_INDEX: base_index},
)

```

The **`base_index`** property preserves declaration order—critical for correct Method Resolution Order (MRO) calculations in languages like Python and C++.

#### Interface Implementation Handling

Classes and enums that implement interfaces trigger **`extract_implemented_interfaces`** at **lines 103-133**. The same deferral pattern applies: `DeferredInherit` with `RELATIONSHIP_TYPE.IMPLEMENTS` for unresolved symbols, or direct `create_implements_relationship` calls when symbols are known.

## Resolving Node Types for Inheritance Edges

A subtle challenge: when emitting an `INHERITS` edge, the graph store requires the parent's node label (`Class`, `Interface`, `Enum`, etc.). The helper **`get_node_type_for_inheritance`** at **lines 39-45** implements this resolution:

- Query the **function registry** for the parent's qualified name
- Default to `NodeType.CLASS` if unregistered
- Pass the resolved type to `ensure_relationship_batch`

This lookup ensures edges connect to nodes with correct labels even when the parent definition is encountered later in the parse order.

## Code Examples

### Typical Pipeline Invocation

This simplified excerpt shows how the structural ingest pass calls `create_class_relationships`:

```python
from codebase_rag.parsers.class_ingest.relationships import create_class_relationships

def ingest_class(node, module_qn: str, language: cs.SupportedLanguage):
    class_qn = f"{module_qn}.{safe_decode_text(node)}"
    create_class_relationships(
        class_node=node,
        class_qn=class_qn,
        module_qn=module_qn,
        node_type=cs.NodeLabel.CLASS,
        is_exported=False,
        language=language,
        class_inheritance=inheritance_dict,      # Mutable: stores parent mappings

        ingestor=graph_ingestor,                  # Implements IngestorProtocol

        import_processor=import_proc,             # Resolves import-relative names

        resolve_to_qn=resolver,                   # Converts names to qualified form

        function_registry=func_registry,          # Symbol table for node type lookup

    )

```

### Manual Edge Creation for Testing

For unit tests or synthetic graph construction, edges can be created directly:

```python
from codebase_rag.parsers.class_ingest.relationships import create_inheritance_relationship
from codebase_rag.constants import NodeLabel

create_inheritance_relationship(
    child_node_type=NodeLabel.CLASS,
    child_qn="my_pkg.Foo",
    parent_qn="my_pkg.Base",
    function_registry=func_registry,  # Resolves "my_pkg.Base" → NodeLabel.CLASS

    ingestor=graph_ingestor,
    base_index=0,                     # First (or only) base class

)

```

## Key Files in the Class Ingest System

| File | Purpose | Critical For |
|------|---------|--------------|
| [`class_ingest/relationships.py`](https://github.com/vitali87/code-graph-rag/blob/main/class_ingest/relationships.py) | Orchestrates `INHERITS`, `IMPLEMENTS`, `EXPORTS` edges; deferred resolution | Multi-language edge emission with cross-file support |
| [`class_ingest/parent_extraction.py`](https://github.com/vitali87/code-graph-rag/blob/main/class_ingest/parent_extraction.py) | Language-specific AST walking for parent discovery | Handling C++, Java, Python, TS/JS, PHP, Rust, Dart syntax |
| [`class_ingest/utils.py`](https://github.com/vitali87/code-graph-rag/blob/main/class_ingest/utils.py) | Shared helpers: `find_child_by_type`, `safe_decode_text` | Robust AST navigation |
| [`constants.py`](https://github.com/vitali87/code-graph-rag/blob/main/constants.py) | `SupportedLanguage`, `RelationshipType`, `NodeLabel` enums | Type-safe graph schema |
| [`services.py`](https://github.com/vitali87/code-graph-rag/blob/main/services.py) | `IngestorProtocol` interface | Decoupled graph store backends |

## Summary

- **[`class_ingest/relationships.py`](https://github.com/vitali87/code-graph-rag/blob/main/class_ingest/relationships.py)** serves as the bridge between tree-sitter ASTs and the code-graph database, converting inheritance syntax into queryable edges
- **Delegation to [`parent_extraction.py`](https://github.com/vitali87/code-graph-rag/blob/main/parent_extraction.py)** isolates language-specific parsing logic from graph construction concerns
- **Deferred edge creation** solves the cross-file reference problem, particularly for C++ headers and incremental ingestion scenarios
- **`base_index` preservation** maintains declaration order for correct method resolution semantics
- **Registry-based node type resolution** ensures edges connect to properly labeled nodes regardless of parse order

## Frequently Asked Questions

### How does code-graph-rag handle C++ multiple inheritance across header files?

The system creates `DeferredCppInherit` records that capture the written base name, a guessed qualified name, and namespace context. These records are stored during the initial parse and resolved in a subsequent pass once all headers are registered, as implemented at **lines 65-75** of [`relationships.py`](https://github.com/vitali87/code-graph-rag/blob/main/relationships.py).

### What prevents duplicate INHERITS edges when re-ingesting modified files?

The `class_inheritance` dictionary passed through the call chain tracks parent mappings per qualified class name. Incremental ingestion workflows use this mapping to rebuild only affected hierarchy subgraphs, avoiding re-emission of edges for unchanged parent relationships.

### Why does base_index matter for inheritance edges?

The `base_index` property (stored in relationship properties at **line 100**) preserves the declaration order of base classes. This ordering is semantically significant for Method Resolution Order in Python and virtual base resolution in C++, enabling accurate downstream analysis of method overrides and diamond inheritance patterns.

### How does the system distinguish between class inheritance and interface implementation?

`create_class_relationships` branches based on AST node type and language semantics. For C#, `split_csharp_bases` explicitly separates the single base class from interfaces. For other languages, `extract_implemented_interfaces` identifies interface clauses, and the code emits `IMPLEMENTS` edges (via `DeferredInherit` or `create_implements_relationship`) rather than `INHERITS` edges at **lines 103-133**.