kglite.okf

Type stubs for kglite.okf — Open Knowledge Format bundle ingestion.

Classes

KnowledgeGraph

A high-performance knowledge graph with typed nodes, connections, and

Functions

build(→ kglite.KnowledgeGraph)

Build a KnowledgeGraph from an OKF bundle directory.

source(→ str)

Read a concept's markdown body on demand (frontmatter stripped).

Package Contents

class kglite.okf.KnowledgeGraph

A high-performance knowledge graph with typed nodes, connections, and a fluent query API backed by Rust.

Single-owner / threading. A KnowledgeGraph is single-owner: it is not safe to share one instance across threads while any thread mutates it (doing so raises a clear RuntimeError). For concurrent access, don’t share the graph — take a thread-safe handle off it: session() (shared reads + serialized writes, plus Session.cursor() for per-thread fluent chains) or freeze() (a lock-free read-only snapshot). See docs/concepts/concurrency.md.

Create an empty KnowledgeGraph.

Parameters:
  • storage – Storage mode. None (default) uses heap-resident storage, optimal for small-to-medium graphs. "mapped" uses mmap-backed columnar storage from the start, designed for large graphs that may approach or exceed available RAM. "disk" uses fully disk-backed storage for very large graphs (100M+ nodes). Requires path.

  • path – Directory path for disk-mode storage. Required when storage="disk". The directory IS the graph — data is written directly to disk via mmap. Load with kglite.load(path).

add_connections(data: pandas.DataFrame | None, connection_type: str, source_type: str, source_id_field: str, target_type: str, target_id_field: str, source_title_field: str | None = None, target_title_field: str | None = None, columns: list[str] | None = None, skip_columns: list[str] | None = None, conflict_handling: str | None = None, column_types: dict[str, str] | None = None, query: str | None = None, extra_properties: dict[str, Any] | None = None) dict[str, Any]

Add connections (edges) between existing nodes.

Two modes — supply either data (a pandas DataFrame) or query (a Cypher string whose RETURN columns provide source/target IDs).

Example (from DataFrame):

graph.add_connections(df, 'KNOWS', 'Person', 'src_id', 'Person', 'tgt_id')

Example (from Cypher query):

graph.add_connections(
    None, 'ENCLOSES', 'Play', 'play_id', 'StructuralElement', 'struct_id',
    query="""
        MATCH (p:Play), (s:StructuralElement)
        WHERE contains(p, s)
        RETURN DISTINCT p.id AS play_id, s.id AS struct_id
    """,
)

Example (query with extra properties):

graph.add_connections(
    None, 'HC_IN_FORMATION', 'Discovery', 'src', 'Stratigraphy', 'tgt',
    query='MATCH ... RETURN d.id AS src, s.id AS tgt',
    extra_properties={'hc_rank': 1},
)
Parameters:
  • data – DataFrame containing edge data, or None when using query.

  • connection_type – Label for this edge type (e.g. 'KNOWS'). Any characters are accepted here — including hyphens/dots/spaces ('supports-claim'). Such a type only needs backtick-quoting when named inside a Cypher query ([r:`supports-claim`]).

  • source_type – Node type of source nodes.

  • source_id_field – Column with source node IDs (must appear in DataFrame or query RETURN).

  • target_type – Node type of target nodes.

  • target_id_field – Column with target node IDs (must appear in DataFrame or query RETURN).

  • source_title_field – Optional title column for source nodes.

  • target_title_field – Optional title column for target nodes.

  • columns – Whitelist of property columns to include (data mode only). Unlike add_nodes(), edge ingestion keeps only id/title columns unless this whitelist is given — so any other edge property column is dropped when columns is omitted. That drop now emits a UserWarning naming the dropped columns; pass columns=[...] to keep them.

  • skip_columns – Columns to exclude (data mode only).

  • conflict_handling'update' (default), 'replace', 'skip', 'preserve', or 'sum'. 'sum' adds numeric edge properties (Int64+Int64, Float64+Float64; mixed promotes to Float64). Non-numeric properties overwrite like 'update'.

  • column_types – Override column dtypes (data mode only).

  • query – Cypher query string (alternative to data). Must be a read-only query whose RETURN clause includes columns matching source_id_field and target_id_field.

  • extra_properties – Dict of static properties to add to every edge created from the query results (query mode only).

Returns:

Operation report dict with connections_created, connections_skipped, etc.

add_connections_bulk(connections: list[dict[str, Any]]) dict[str, int]

Add multiple connection types at once.

Each dict must contain source_type, target_type, connection_name, and data (DataFrame with source_id/target_id columns).

Returns:

Mapping of connection_name to count of connections created.

add_connections_from_source(connections: list[dict[str, Any]]) dict[str, int]

Add connections, auto-filtering to types already loaded in the graph.

Same spec format as add_connections_bulk(), but silently skips connection specs whose source or target type is not in the graph.

Returns:

Mapping of connection_name to count of connections created.

add_embeddings(node_type: str, text_column: str, embeddings: dict[Any, list[float]], metric: str | None = None) dict[str, Any]

Add or update embeddings without discarding the existing store.

Differs from set_embeddings() (which replaces the store) by upserting entries into an existing (node_type, "{text_column}_emb") store. If no store exists yet, behaves like set_embeddings — the first call creates one; subsequent calls extend it.

Use this for incremental ingest workflows where multiple add_nodes + embedding batches need to coexist without a read-merge-write cycle through the user’s process.

Parameters:
  • node_type – The node type (e.g. 'Article').

  • text_column – Source text column name (e.g. 'summary').

  • embeddings – Dict mapping node IDs to embedding vectors.

  • metric – Only used when the store doesn’t exist yet. Ignored otherwise (the existing store’s metric is preserved).

Returns:

Dict with embeddings_stored (total in store after the upsert), dimension, skipped (unknown ids), and store_created (True iff this call created the store).

add_label(node_type: str, ids: list[Any], label: str) dict[str, int]

Add a secondary label to a batch of nodes by id.

Secondary labels are queryable via Cypher (MATCH (n:Label)) and surfaced by labels(n). The primary type (set via add_nodes(node_type=...)) is immutable — to retype a node, use SET n.type = 'NewType'.

Parameters:
  • node_type – Primary type of the nodes.

  • ids – Node ids (the unique_id_field values).

  • label – Secondary label to add.

Returns:

Dict with labelled (newly added) and skipped (unknown ids, or label already present).

Example:

graph.add_label('Agent', ['ag_001', 'ag_002'], 'Reviewer')
graph.cypher('MATCH (a:Reviewer) RETURN a.id').to_list()
add_nodes(data: pandas.DataFrame, node_type: str, unique_id_field: str, node_title_field: str | None = None, columns: list[str] | None = None, conflict_handling: str | None = None, skip_columns: list[str] | None = None, column_types: dict[str, str] | None = None, timeseries: dict[str, Any] | None = None, nullable_int_downcast: bool = False, labels: list[str] | None = None, managed_reload: bool = False) dict[str, Any]

Add nodes from a DataFrame.

String and integer IDs are auto-detected from the DataFrame dtype. Non-contiguous DataFrame indexes (e.g. from filtering) are handled automatically.

When timeseries is provided, the DataFrame may contain multiple rows per unique ID (one per time step). Rows are deduplicated automatically — the first occurrence per ID provides static node properties, and all rows contribute to the timeseries channels.

Parameters:
  • data – DataFrame containing node data.

  • node_type – Label for this set of nodes (e.g. 'Person').

  • unique_id_field – Column used as unique identifier.

  • node_title_field – Column used as display title. Defaults to unique_id_field.

  • columns – Whitelist of columns to include. None = all.

  • conflict_handling

    'update' (default), 'replace', 'skip', 'preserve', or 'sum'. 'sum' acts as 'update' for nodes.

    Partial-update guarantee: 'update' writes only the columns present in this call’s data — properties of an existing node that are not in the incoming columns are left untouched. This is a stable contract: it lets a batch reload re-assert a subset of fields (e.g. identity + links) without clobbering fields another writer owns (e.g. an agent’s status/notes). 'replace' instead reconciles the node to the incoming record — it overwrites the whole node, so a property absent from the new data is dropped (set to null). Use 'replace' (not 'update') when the source data is the single source of truth and you want field deletions to propagate on rebuild.

  • skip_columns – Columns to exclude.

  • column_types – Override column dtypes, e.g. {'col': 'string'}. Supported: 'string', 'integer', 'float', 'datetime', 'timestamp', 'uniqueid', 'list', 'map'. A column of Python lists/tuples is auto-detected as a native 'list' property (stored structurally, not stringified), so 'y' IN n.aliases tests membership and UNWIND n.aliases yields the elements; pass 'list' explicitly to force it. A column of Python dicts is auto-detected as a native 'map' property (n.meta['k'] / n.meta.k read back the value) rather than being stringified. A datetime64 column keeps its full time-of-day when any value has a nonzero time (stored as a Timestamp); a pure-midnight column stays date-only ('datetime'). Pass 'timestamp' to force full date+time, 'datetime' to force date-only. Also supports spatial types: 'location.lat', 'location.lon', 'geometry', 'point.<name>.lat', 'point.<name>.lon', 'shape.<name>'.

  • nullable_int_downcast – When True, Float64 columns whose non-null values are all integer-valued (e.g. pd.NA-bearing ints that pandas auto-promoted to float64) are silently downcast to Int64. Default False — explicit opt-in protects existing callers.

  • managed_reload – When True, this call is part of a managed reload (a batch writer rebuilding from source). If node_type declares layer='runtime' in the schema (an agent-owned type), the write is skipped as a no-op and reported — so a research rebuild can never clobber agent-owned nodes. Undeclared or layer='managed' types are written normally. Pairs with the layer declaration in define_schema and conflict_handling.

  • timeseries

    Inline timeseries configuration dict with keys:

    • time (required): column name containing date strings ('yyyy-mm', 'yyyy-mm-dd', 'yyyy-mm-dd hh:mm'), or a dict mapping year/month/day/hour/minute to column names (e.g. {'year': 'ar', 'month': 'maned'}).

    • channels (required): list of column names for timeseries data (e.g. ['oil', 'gas', 'condensate']).

    • resolution (optional): 'year', 'month', 'day', 'hour', or 'minute'. Auto-detected from time format if omitted.

    • units (optional): dict mapping channel names to unit strings (e.g. {'oil': 'MSm3'}).

  • labels – Optional secondary labels to apply to every node in the batch. add_nodes(df, 'Agent', 'id', 'name', labels=['Reviewer']) creates Agent-typed nodes that also wear the Reviewer label, queryable via MATCH (a:Reviewer) or MATCH (a:Agent:Reviewer). For per-row labels, call add_label() after.

Returns:

Operation report dict with keys nodes_created, nodes_updated, nodes_skipped, processing_time_ms, has_errors, and optionally errors with skip reasons.

Example:

graph.add_nodes(df, 'Production', 'field_id', 'field_name',
    timeseries={
        'time': 'date',
        'channels': ['oil', 'gas', 'condensate', 'oe'],
    })
add_nodes_bulk(nodes: list[dict[str, Any]]) dict[str, int]

Add multiple node types at once.

Each dict in nodes must contain node_type, unique_id_field, node_title_field, and data (a DataFrame).

Returns:

Mapping of node_type to count of nodes added.

add_properties(properties: dict[str, list[str] | dict[str, str]], keep_selection: bool | None = None) KnowledgeGraph

Enrich selected nodes with properties from ancestor nodes in the traversal chain.

Copies, renames, aggregates, or computes spatial properties from nodes at other levels of the selection hierarchy onto the current leaf nodes.

Parameters:
  • properties

    Dict mapping source node type → property spec:

    • {'B': ['name', 'status']} — copy listed properties as-is

    • {'B': []} — copy all properties from B

    • {'B': {'new_name': 'old_name'}} — copy with rename

    • {'B': {'avg_depth': 'mean(depth)'}} — aggregate functions: count(*), sum(prop), mean(prop), min(prop), max(prop), std(prop), collect(prop)

    • {'B': {'dist': 'distance'}} — spatial compute: distance, area, perimeter, centroid_lat, centroid_lon

  • keep_selection – Preserve current selection. Default True.

Returns:

A new KnowledgeGraph with the properties added to selected nodes.

Examples:

# Copy structure name onto wells
graph.select('Structure').compare('Well', 'contains') \
    .add_properties({'Structure': ['name', 'status']})

# Rename properties
graph.select('Structure').compare('Well', 'contains') \
    .add_properties({'Structure': {'struct_name': 'name'}})

# Aggregate with Agg helpers (discoverable via autocomplete)
from kglite import Agg, Spatial
graph.select('Structure').compare('Well', 'contains') \
    .add_properties({'Well': {
        'well_count': Agg.count(),
        'avg_depth': Agg.mean('depth'),
    }})

# Spatial compute with Spatial helpers
graph.select('Structure').compare('Well', 'contains') \
    .add_properties({'Structure': {
        'dist_to_center': Spatial.distance(),
        'parent_area': Spatial.area(),
    }})
add_timeseries(node_type: str, *, data: Any, fk: str, time_key: list[str], channels: dict[str, str] | list[str], resolution: str | None = None, units: dict[str, str] | None = None) dict[str, Any]

Bulk-load timeseries data from a DataFrame.

Groups rows by fk, sorts by time_key, and attaches the resulting timeseries to matching nodes (found by node ID).

Time keys are combined into NaiveDate internally: - Single column: parsed as date strings ('2020-06') - Multiple columns: combined as year + month [+ day] → NaiveDate

Parameters:
  • node_type – Target node type.

  • data – Source DataFrame.

  • fk – Foreign key column in data linking to node IDs.

  • time_key – Column(s) for time keys. If single column, values are parsed as date strings. If multiple, combined as year + month [+ day].

  • channels – Either a list of column names (used as channel names) or a dict mapping {channel_name: column_name}.

  • resolution – Time granularity ('year', 'month', 'day'). Auto-detected from time_key count if not specified.

  • units – Optional channel→unit map, merged into config.

Returns:

{'nodes_loaded': N, 'total_records': M, 'total_rows': R}.

Return type:

Summary

add_ts_channel(node_id: Any, channel_name: str, values: list[float]) None

Add a timeseries channel to a node.

The node must already have a time index set (via set_time_index or add_timeseries). The values length must match the time index length. Use float('nan') for missing values.

Parameters:
  • node_id – The node’s unique ID.

  • channel_name – Channel name (e.g. 'oil', 'temperature').

  • values – Float values aligned with the time index.

all_paths(source_type: str, source_id: Any, target_type: str, target_id: Any, max_hops: int | None = None, max_results: int | None = None, connection_types: list[str] | None = None, via_types: list[str] | None = None, timeout_ms: int | None = None) list[dict[str, Any]]

Find all paths between two nodes.

Parameters:
  • source_type – Source node type.

  • source_id – Source node ID.

  • target_type – Target node type.

  • target_id – Target node ID.

  • max_hops – Maximum path length. Default 5.

  • max_results – Stop after finding this many paths. Default unlimited. Use to prevent OOM on dense graphs.

  • connection_types – Only traverse edges of these types. Default all.

  • via_types – Only traverse through nodes of these types. Default all.

  • timeout_ms – Abort after this many milliseconds, returning partial results.

Returns:

List of path dicts, each with path, connections, length.

are_connected(source_type: str, source_id: Any, target_type: str, target_id: Any) bool

Check if two nodes are connected (directly or indirectly).

begin(timeout_ms: int | None = None) Transaction

Begin a read-write transaction — returns a Transaction with a working copy.

Creates a snapshot (deep-clone) of the current graph state. All mutations within the transaction are isolated until commit() is called. If rolled back (or dropped without committing), no changes are applied.

Uses optimistic concurrency control: commit() will raise RuntimeError if the graph was modified by another transaction since begin() was called.

Parameters:

timeout_ms – Optional transaction-level timeout in milliseconds. If set, all operations after the deadline raise TimeoutError.

Can be used as a context manager:

with graph.begin() as tx:
    tx.cypher("CREATE (n:Person {name: 'Alice', age: 30})")
    tx.cypher("CREATE (n:Person {name: 'Bob', age: 25})")
    # auto-commits on success, auto-rollbacks on exception
begin_read(timeout_ms: int | None = None) Transaction

Begin a read-only transaction — O(1) cost, zero memory overhead.

Returns a Transaction backed by an Arc reference to the current graph state. Mutations (CREATE, SET, DELETE, REMOVE, MERGE) are rejected.

Ideal for concurrent read-heavy workloads (e.g. MCP server agents) where you want a consistent snapshot without the cost of a full clone.

Parameters:

timeout_ms – Optional transaction-level timeout in milliseconds.

Can be used as a context manager:

with graph.begin_read() as tx:
    result = tx.cypher("MATCH (n:Person) RETURN n.name")
    # auto-closes on exit (no commit needed)
betweenness_centrality(normalized: bool | None = None, sample_size: int | None = None, connection_types: str | list[str] | None = None, top_k: int | None = None, as_dict: bool | None = None, timeout_ms: int | None = None, to_df: bool | None = None) ResultView | dict[Any, float] | pandas.DataFrame

Calculate betweenness centrality.

Parameters:
  • normalized – Normalise scores to [0, 1]. Default True.

  • sample_size – Sample source nodes for faster computation on large graphs.

  • connection_types – Only traverse these relationship types (str or list).

  • top_k – Return only the top K nodes.

  • as_dict – Return {id: score} dict instead of list of dicts.

  • timeout_ms – Abort after this many milliseconds, returning partial results.

  • to_df – Return a pandas DataFrame with columns type, title, id, score.

Returns:

List of dicts with type, title, id, score, sorted by score descending. Or a DataFrame if to_df=True.

bounds(lat_field: str | None = None, lon_field: str | None = None, as_shapely: bool = False) dict[str, float] | Any | None

Get geographic bounds of selected nodes.

Parameters:
  • lat_field – Latitude property name. Default 'latitude'.

  • lon_field – Longitude property name. Default 'longitude'.

  • as_shapely – If True, return a shapely.geometry.Polygon (box) instead of a dict.

Returns:

Dict with min_lat, max_lat, min_lon, max_lon, or a shapely box polygon when as_shapely=True, or None if no valid coordinates found.

bug_report(query: str, result: str, expected: str, description: str, path: str | None = None) str

File a Cypher bug report to reported_bugs.md.

Appends a timestamped, version-tagged report to the top of the file (creating it if needed). All inputs are sanitised against code injection (HTML tags, javascript: URIs, triple-backtick breakout).

Parameters:
  • query – The Cypher query that triggered the bug.

  • result – The actual result you got.

  • expected – The result you expected.

  • description – Free-text explanation.

  • path – Optional file path (default: reported_bugs.md in cwd).

Returns:

Confirmation message with the file path.

Raises:

IOError – If the file cannot be written.

build_id_indices(node_types: list[str] | None = None) None

Pre-build ID lookup indices for fast node() calls.

Parameters:

node_types – Types to index. None indexes all types.

build_vector_index(node_type: str, text_column: str, m: int | None = None, ef_construction: int | None = None, ef_search: int | None = None, metric: str | None = None) dict[str, Any]

Build an HNSW approximate-nearest-neighbour index over an embedding store so vector search scales sub-linearly on large stores.

Opt-in, like create_index(): without it, vector search is an exact brute-force scan. Once built, vector_search() / search_text() auto-use the index for whole-corpus queries on large stores; pass exact=True to force an exact scan. The index is dropped automatically whenever the store’s vectors change (add_embeddings / embed_texts) or slots are remapped (vacuum) — rebuild it after.

Parameters:
  • node_type – The node type (e.g. 'Article').

  • text_column – Source column name (e.g. 'summary'; the store is '{text_column}_emb').

  • m – Max neighbours per node on upper layers (default 16). Higher → better recall, larger index.

  • ef_construction – Build-time search width (default 200). Higher → better graph, slower build.

  • ef_search – Default query-time search width (default 64). Higher → better recall, slower query.

  • metric'cosine' (default), 'dot_product', or 'euclidean'. 'poincare' is unsupported (stays exact). If omitted, uses the store’s metric, else 'cosine'.

Returns:

{'indexed': int, 'metric': str, 'm': int}.

Return type:

dict

Raises:

ValueError – if the store doesn’t exist or the metric is unsupported.

Example:

g.embed_texts("Article", "summary")
g.build_vector_index("Article", "summary")          # opt in
hits = g.select("Article").search_text("summary", "AI", top_k=10)
calculate(expression: str, level_index: int | None = None, store_as: str | None = None, keep_selection: bool | None = None, aggregate_connections: bool | None = None) Any

Evaluate a mathematical expression on selected nodes.

Supports property references, arithmetic operators, and aggregate functions (sum, mean, std, min, max, count).

Parameters:
  • expression – Expression string, e.g. 'price * quantity' or 'mean(age)'.

  • level_index – Target level in the hierarchy.

  • store_as – If set, stores results as this property on nodes.

  • keep_selection – Preserve selection after store. Default False.

  • aggregate_connections – Aggregate over connected nodes.

Returns:

Computation results, or a KnowledgeGraph if store_as is set.

centroid(lat_field: str | None = None, lon_field: str | None = None, as_shapely: bool = False) dict[str, float] | Any | None

Get the geographic centroid (average lat/lon) of selected nodes.

Parameters:
  • lat_field – Latitude property name. Default 'latitude'.

  • lon_field – Longitude property name. Default 'longitude'.

  • as_shapely – If True, return a shapely.geometry.Point instead of a dict.

Returns:

Dict with latitude and longitude, or a shapely Point when as_shapely=True, or None.

clear() None

Clear the current selection (resets to empty).

clear_schema() KnowledgeGraph

Remove the schema definition from the graph.

close() None

Persist the graph to its remembered origin path (the file it was opened from via kglite.open() / kglite.load(), or last saved to). No-op if the graph has no associated path. The graph stays usable after close().

closeness_centrality(normalized: bool | None = None, sample_size: int | None = None, connection_types: str | list[str] | None = None, top_k: int | None = None, as_dict: bool | None = None, timeout_ms: int | None = None, to_df: bool | None = None) ResultView | dict[Any, float] | pandas.DataFrame

Calculate closeness centrality.

Parameters:
  • normalized – Adjust for disconnected components. Default True.

  • sample_size – Approximate by sampling N source nodes (faster for large graphs). If None, uses all nodes.

  • connection_types – Filter to specific relationship types.

  • top_k – Return only the top K nodes.

  • as_dict – Return {id: score} dict instead of list of dicts.

  • timeout_ms – Abort after this many milliseconds, returning partial results.

  • to_df – Return a pandas DataFrame with columns type, title, id, score.

Returns:

List of dicts with type, title, id, score.

collect(limit: int | None = None) ResultView

Materialise selected nodes as a flat ResultView.

For grouped output by parent type, use collect_grouped() instead.

Parameters:

limit – Maximum number of nodes to return.

Returns:

A ResultView containing id, title, type, and all stored properties for each selected node.

collect_children(property: str | None = None, where: dict[str, Any] | None = None, sort: str | list[tuple[str, bool]] | None = None, limit: int | None = None, store_as: str | None = None, max_length: int | None = None, keep_selection: bool | None = None) Any

Collect child-node property values into comma-separated lists.

Parameters:
  • property – Child property to collect. Default 'title'.

  • where – Filter conditions for children.

  • sort – Sort children.

  • limit – Limit children per parent.

  • store_as – If set, stores the list as this property on parent nodes.

  • max_length – Max string length when storing.

  • keep_selection – Preserve selection after store. Default False.

Returns:

Dict of {parent_title: 'val1, val2, ...'} or a KnowledgeGraph if store_as is set.

collect_grouped(group_by: str, *, parent_info: bool = False, flatten_single_parent: bool = True, limit: int | None = None) dict[str, Any]

Materialise selected nodes grouped by a parent type in the traversal hierarchy.

Parameters:
  • group_by – Parent node type to group by (must exist in the traversal chain).

  • parent_info – Include parent metadata (type, id, title) in each group. Default False.

  • flatten_single_parent – If only one parent group exists, return a flat list instead of a single-key dict. Default True.

  • limit – Maximum number of nodes to return.

Returns:

Dict mapping parent title → list of node dicts. If flatten_single_parent is True and there is only one parent, returns a flat list.

Examples:

# Group wells by their parent field
graph.select('Field').traverse('HAS_WELL') \
    .collect_grouped('Field')
# → {'TROLL': [...], 'EKOFISK': [...]}

# Include parent metadata
graph.select('Field').traverse('HAS_WELL') \
    .collect_grouped('Field', parent_info=True)
compact() int

Compact a disk-mode graph: merge overflow edges back into CSR arrays.

Overflow edges accumulate when edges are added after the initial CSR build (e.g., after loading a graph and adding new connections). Compaction rebuilds the CSR to include all overflow edges, restoring optimal query performance.

Returns:

Number of overflow edges that were merged. Returns 0 if there are no overflow edges or the graph is not in disk mode.

compare(target_type: str | list[str], method: str | dict[str, Any], *, filter: dict[str, Any] | None = None, sort: str | list[tuple[str, bool]] | None = None, limit: int | None = None, level_index: int | None = None, new_level: bool | None = None) KnowledgeGraph

Compare selected nodes against a target type using spatial, semantic, or clustering methods.

Parameters:
  • target_type – Node type to compare against (e.g. 'Well').

  • method

    Comparison method — a string shorthand or a dict with settings:

    Spatial methods:

    • 'contains' — point-in-polygon or polygon containment

    • 'intersects' — polygon-polygon intersection

    • {'type': 'distance', 'max_m': 5000} — geodesic distance

    Semantic methods:

    • {'type': 'text_score', 'property': 'name'} — embedding similarity

    • {'type': 'text_score', 'threshold': 0.7} — with threshold

    • {'type': 'text_score', 'metric': 'poincare'} — Poincaré distance (hierarchical data)

    Clustering methods:

    • {'type': 'cluster', 'k': 5} — K-means clustering

    • {'type': 'cluster', 'algorithm': 'dbscan', 'eps': 0.5} — DBSCAN

    Common dict keys:

    • resolve: 'centroid', 'closest', or 'geometry'

    • max_m: Maximum distance in meters (distance method)

    • threshold: Minimum similarity score (semantic methods)

    • k: Number of clusters (K-means)

    • features: Properties to cluster on

  • filter – Property conditions for target nodes.

  • sort – Sort results. Field name or [(field, ascending)] list.

  • limit – Max target nodes per source.

  • level_index – Source level in the hierarchy (advanced).

  • new_level – Add targets as new hierarchy level. Default True.

Returns:

A new KnowledgeGraph with comparison results selected.

Examples:

# Spatial containment: find wells inside structures
graph.select('Structure').compare('Well', 'contains')

# Distance: wells within 5km
graph.select('Well').compare('Well',
    {'type': 'distance', 'max_m': 5000})

# Semantic similarity
graph.select('Document').compare('Document',
    {'type': 'text_score', 'property': 'summary', 'threshold': 0.7})

# Clustering
graph.select('Well').compare('Well',
    {'type': 'cluster', 'k': 5, 'features': ['latitude', 'longitude']})
composite_index_stats(node_type: str, properties: list[str]) dict[str, Any] | None

Get statistics for a composite index. Returns None if not found.

connected_components(weak: bool | None = None, titles_only: bool | None = None) list[list[dict[str, Any]]]

Find connected components in the graph.

Parameters:
  • weak – If True (default), find weakly connected components. If False, find strongly connected components.

  • titles_only – If True, return lists of node titles instead of full dicts.

Returns:

List of components (largest first), each a list of node info dicts.

connection_types() list[dict[str, Any]]

Return all connection types with counts and endpoint type sets.

Returns:

List of dicts with type, count, source_types, target_types.

connections(indices: list[int] | None = None, parent_info: bool | None = None, include_node_properties: bool | None = None, flatten_single_parent: bool = True) dict[str, Any]

Get connections for selected nodes.

Parameters:
  • indices – Specific node indices to query.

  • parent_info – Include parent info in output.

  • include_node_properties – Include properties of connected nodes. Default True.

  • flatten_single_parent – Flatten when only one parent. Default True.

Returns:

Nested dict {title: {node_id, type, incoming, outgoing}}.

contains_point(lat: float, lon: float, geometry_field: str | None = None) KnowledgeGraph

Filter nodes whose WKT polygon contains a point.

Parameters:
  • lat – Query point latitude.

  • lon – Query point longitude.

  • geometry_field – WKT geometry property name. Default 'geometry'.

Returns:

A new KnowledgeGraph with containing nodes.

context(name: str, node_type: str | None = None, hops: int | None = None) dict[str, Any]

Get the full neighborhood of a code entity.

Returns the node’s properties and all related entities grouped by relationship type. If the name is ambiguous, returns the matches so you can refine with a qualified name.

Parameters:
  • name – Entity name or qualified name.

  • node_type – Optional node type hint.

  • hops – Max traversal depth (default 1).

Returns:

Dict with "node" (properties), "defined_in" (file path), and relationship groups (e.g. "HAS_METHOD", "CALLS", "called_by").

copy() KnowledgeGraph

Create an independent deep copy of this graph.

Returns a new KnowledgeGraph that shares no mutable state with the original. Useful for running mutations without affecting the source graph.

copy_embeddings_from(other: KnowledgeGraph) dict[str, int]

Copy every embedding store from other into this graph, by node id.

The one-call answer to the “rebuild a fresh graph from a source of truth on each load, keep the vectors” workflow: build the new graph, then new.copy_embeddings_from(old). Vectors land on the new nodes that share an id, carrying each store’s dimension, metric, model id, and per-node text hashes — so a following embed_texts(mode='changed') re-embeds only genuinely new/changed text. Vectors whose id has no matching node here are skipped (counted). Replaces the manual embeddings()add_embeddings()embed_texts() carry.

Returns:

Dict with stores_copied, vectors_copied, vectors_skipped.

Example:

new = build_graph_from_source()          # fresh, no vectors
new.copy_embeddings_from(old)             # carry vectors by id
new.embed_texts("Doc", "summary", mode="changed")  # fill only the new/changed
count(level_index: int | None = None, group_by_parent: bool | None = None, store_as: str | None = None, keep_selection: bool | None = None, group_by: str | None = None) Any

Count nodes, optionally grouped by parent or by a property.

Parameters:
  • level_index – Target level in the hierarchy.

  • group_by_parent – Group counts by parent node.

  • store_as – Store count as a property on parent nodes.

  • keep_selection – Preserve selection after store. Default False.

  • group_by – Group counts by this property instead of by parent. Returns {group_value: count}.

Returns:

An integer count, grouped counts, or a KnowledgeGraph if store_as is set.

create_composite_index(node_type: str, properties: list[str]) dict[str, Any]

Create a composite index on multiple properties.

Parameters:
  • node_type – Node type to index.

  • properties – List of property names for the composite key.

Returns:

Dict with type, properties, unique_combinations.

create_connections(connection_type: str, keep_selection: bool | None = None, conflict_handling: str | None = None, properties: dict[str, list[str]] | None = None, source_type: str | None = None, target_type: str | None = None) KnowledgeGraph

Create connections from the traversal hierarchy.

By default, creates edges from the top-level ancestor (first traversal level) to the leaf nodes (last level). Use source_type / target_type to choose different levels.

Parameters:
  • connection_type – Label for the new edges (e.g. 'A_TO_C').

  • keep_selection – Preserve selection. Default False.

  • conflict_handling'update' (default), 'replace', 'skip', 'preserve', or 'sum'.

  • properties – Copy properties from intermediate nodes onto the new edges. Dict mapping node type to property names: {'TypeB': ['score', 'weight']}. An empty list copies all properties from that type.

  • source_type – Node type to use as source (default: first level).

  • target_type – Node type to use as target (default: last level).

Returns:

A new KnowledgeGraph with the connections added.

Example:

# After traversal A → B → C, create direct A → C edges
# with B's 'score' property copied onto each edge
graph.select('A') \
    .traverse('REL_AB') \
    .traverse('REL_BC') \
    .create_connections('A_TO_C',
        properties={'B': ['score']})
create_global_index(property: str) dict[str, Any]

Build a cross-type global index on property.

Unlike create_index, which is keyed by (node_type, property), this indexes EVERY node whose value at property is a non-empty string — regardless of node type. Enables:

  • MATCH (n {label: 'Norway'}) — untyped Cypher lookups, routed through the global index in O(log N).

  • graph.search(text) — top-k helper that returns the nodes whose label (or any indexed property) matches.

Disk-backed graphs only. On memory/mapped graphs this is a no-op that returns unique_values=0 — per-type create_index already covers the use case at in-memory scale.

Returns:

Dict with property, unique_values (count of nodes indexed), and created.

create_index(node_type: str, property: str) dict[str, Any]

Create an index on a property for O(1) equality filter lookups.

Indexes are automatically maintained by Cypher mutations (CREATE, SET, REMOVE, DELETE, MERGE).

Idempotent — re-creating an existing index rebuilds it without error; created is then False. It is True only when this call made a new index.

Parameters:
  • node_type – Node type to index.

  • property – Property name to index.

Returns:

Dict with node_type, property, unique_values, persistent, and created (False if the index already existed).

create_range_index(node_type: str, property: str) dict[str, Any]

Create a range index (B-Tree) on a property for efficient range queries.

Enables fast >, >=, <, <=, and BETWEEN queries in filter() calls.

Parameters:
  • node_type – Node type to index.

  • property – Property name to index.

Returns:

Dict with type, property, unique_values, created.

Example:

graph.create_range_index('Person', 'age')
old = graph.select('Person').where({'age': {'>': 60}}).collect()
cypher(query: str, *, to_df: bool = False, params: dict[str, Any] | None = None, timeout_ms: int | None = None, max_rows: int | None = None, streaming: bool = True, disable_optimizer: bool = False, disabled_passes: list[str] | None = None, write_scope: list[str] | None = None, git_sha: str | None = None, modified_by: str | None = None) ResultView | pandas.DataFrame | str

Execute a Cypher query.

Supports MATCH, WHERE, RETURN, ORDER BY, LIMIT, SKIP, WITH, OPTIONAL MATCH, UNWIND, UNION, CREATE, SET, DELETE, DETACH DELETE, REMOVE, MERGE (with ON CREATE SET / ON MATCH SET), HAVING, CASE expressions, WHERE EXISTS, shortestPath(), list comprehensions, CALL { … } read subqueries (uncorrelated + correlated; v1 excludes writes / UNION / unit subqueries in the body), CALL…YIELD (graph algorithms: pagerank, betweenness, degree, closeness, louvain, label_propagation, connected_components), parameters ($param), != operator, aggregation functions, window functions (row_number(), rank(), dense_rank() with OVER (PARTITION BY ... ORDER BY ...)), and date arithmetic (date + N, date - date, date_diff(d1, d2)).

Mutation queries (CREATE, SET, DELETE, REMOVE, MERGE) store statistics on graph.last_mutation_stats with keys nodes_created, relationships_created, properties_set, nodes_deleted, relationships_deleted, properties_removed.

Each call is atomic: if any clause fails, the graph is unchanged. Property and composite indexes are automatically maintained.

FORMAT CSV: Append FORMAT CSV to any query to get results as a CSV string instead of a ResultView. Good for large result transfers and token-efficient LLM consumption in MCP servers.

Before execution, the query is validated against the graph schema (known node types, connection types, and properties). Unknown identifiers raise ValueError with a Did you mean '...'? suggestion — catches typos before any scan runs.

Parameters:
  • query – Cypher query string.

  • to_df – If True, return a pandas DataFrame.

  • params – Optional parameter dict for $param substitution.

  • timeout_ms – Deadline in milliseconds. If omitted, uses set_default_timeout() when set, otherwise the built-in default of 180_000 ms (3 min). Pass 0 to disable the deadline for this call. Independently of the deadline, a long-running read can be interrupted with Ctrl-C (raises KeyboardInterrupt) on POSIX — the query aborts promptly instead of waiting for the deadline.

  • max_rows – Cap on intermediate rows; defaults to set_default_max_rows().

  • streaming – When True (default), the executor absorbs compatible clause runs (currently WITH/RETURN(group, agg) [ORDER BY ... LIMIT k]) into a streaming pipeline that builds aggregate state inline and replaces full sort + truncate with a heap-pruned top-K (O(n log k) instead of O(n log n)). Pass False to force the materialized executor — useful for debugging parity issues; behaviour should otherwise be identical.

  • disable_optimizer – When True, run the query with all optimizer passes skipped — schema validation still applies, but no predicate pushdown, fusion, reordering, or LIMIT-pushdown happens. Diagnostic / testing knob; normal use should leave this False. Used by the differential test harness to assert optimized and naive executions produce identical results.

  • disabled_passes – Skip a specific subset of optimizer passes by name. Names must come from kglite.cypher_pass_names() — typos raise ValueError. Useful for bisecting which pass introduces a divergence.

  • write_scope – Role-scoped write whitelist. When given, a CREATE/SET/MERGE that creates or mutates a node whose type is not in the list is rejected (integrity, not secrecy — e.g. a coding role may write ["Plan", "Task"] but not research-owned Algorithm nodes). Creating an edge between already-existing (matched) nodes is allowed even when an endpoint’s type is out of scope — linking to a node does not mutate it — but creating a new out-of-scope endpoint node is still rejected. None (default) = unrestricted. Applies per-call; also on Session.execute() and Transaction.cypher.

Returns:

ResultView by default, DataFrame when to_df=True, or CSV string when the query ends with FORMAT CSV.

Raises:
  • KeyboardInterrupt – If a long-running read is interrupted with Ctrl-C (POSIX only). The graph is left unchanged.

  • kglite.CypherSyntaxError / kglite.SchemaError / ... – Typed kglite.KgError subclasses for query / schema faults.

Example:

rows = graph.cypher('''
    MATCH (p:Person)-[:KNOWS]->(f:Person)
    WHERE p.age > $min_age
    RETURN p.name, count(f) AS friends
    ORDER BY friends DESC LIMIT 10
''', params={'min_age': 25})
for row in rows:
    print(row['name'], row['friends'])

# As DataFrame
df = graph.cypher('MATCH (n:Person) RETURN n.name, n.age', to_df=True)

# As CSV string (good for large data transfers)
csv = graph.cypher('MATCH (n:Person) RETURN n.name, n.age FORMAT CSV')

# CREATE nodes and edges
graph.cypher("CREATE (n:Person {name: 'Alice', age: 30})")
print(graph.last_mutation_stats['nodes_created'])  # 1

# SET properties
graph.cypher('''
    MATCH (n:Person) WHERE n.name = 'Alice'
    SET n.city = 'Oslo', n.age = 31
''')

# Semantic search with text_score (requires set_embedder + embed_texts)
results = graph.cypher('''
    MATCH (n:Article)
    RETURN n.title,
           text_score(n, 'summary', 'machine learning') AS score
    ORDER BY score DESC LIMIT 10
''', to_df=True)

# text_score with parameter
graph.cypher('''
    MATCH (n:Article)
    WHERE text_score(n, 'summary', $query) > 0.8
    RETURN n.title
''', params={'query': 'artificial intelligence'})

# CALL graph algorithms
top = graph.cypher('''
    CALL pagerank() YIELD node, score
    RETURN node.title, score
    ORDER BY score DESC LIMIT 10
''')

# Community detection
graph.cypher('''
    CALL louvain() YIELD node, community
    RETURN community, count(*) AS size
    ORDER BY size DESC
''')
date(date_str: str | None = None, end_str: str | None = None) KnowledgeGraph

Set the temporal context for auto-filtering.

Returns a new KnowledgeGraph. All subsequent select() and traverse() calls on the returned graph use this context for temporal filtering.

Modes:
  • date('2013') — point-in-time (valid at 2013-01-01).

  • date('2010', '2015') — range: include everything valid at any point during 2010-01-01 to 2015-12-31 (overlap check).

  • date('all') — disable temporal filtering entirely.

  • date() — reset to today (default).

Parameters:
  • date_str – Date string, 'all', or None to reset.

  • end_str – Optional end date for range mode. End dates expand to period end ('2015' → 2015-12-31, '2015-06' → 2015-06-30).

Returns:

A new KnowledgeGraph with the given temporal context.

define_schema(schema_dict: dict[str, Any]) KnowledgeGraph

Define the expected schema for the graph.

Replaces the entire schema — this is not a merge. Any previous define_schema call is fully superseded, so a call with a subset of types drops the types it omits. Redefine all types each call (or read the current schema via schema_definition() and merge yourself).

Parameters:

schema_dict

Schema definition with nodes and connections keys. Each node entry may set required/optional/types and, optionally, primary_key to declare an enforced PRIMARY KEY:

g.define_schema({"nodes": {"Person": {"primary_key": "id"}}})

Declaring primary_key makes the write path enforce uniqueness on that key — a CREATE that would duplicate it is rejected (use MERGE to upsert). It is opt-in: types without a declared primary_key keep the permissive default. For now the key must be "id" (the identity field); an enforced PK on an arbitrary property is not yet supported.

A node entry may also set layer to 'managed' (rebuilt from source by a batch writer) or 'runtime' (owned/mutated live by another writer, e.g. an agent). With layers declared, an add_nodes(..., managed_reload=True) call refuses to write a runtime type — enforcing disjoint ownership in a two-writer contract graph:

g.define_schema({"nodes": {
    "AlgorithmSpec": {"layer": "managed"},
    "Task":         {"layer": "runtime"},
}})

A node entry may also set auto_timestamp: True to opt that type into freshness provenance: every write (Cypher CREATE/SET/MERGE and add_nodes) auto-stamps an updated_at timestamp, plus the caller-supplied git_sha / modified_by when provided. It is off by default (writes stay deterministic) and independent of layer / lock_schema:

g.define_schema({"nodes": {"Task": {"auto_timestamp": True}}})

Returns:

Self with schema defined.

degree_centrality(normalized: bool | None = None, connection_types: str | list[str] | None = None, top_k: int | None = None, as_dict: bool | None = None, timeout_ms: int | None = None, to_df: bool | None = None) ResultView | dict[Any, float] | pandas.DataFrame

Calculate degree centrality.

Parameters:
  • normalized – Normalise by (n-1). Default True.

  • connection_types – Only count these relationship types (str or list).

  • top_k – Return only the top K nodes.

  • as_dict – Return {id: score} dict instead of list of dicts.

  • timeout_ms – Abort after this many milliseconds, returning partial results.

  • to_df – Return a pandas DataFrame with columns type, title, id, score.

Returns:

List of dicts with type, title, id, score. Or a DataFrame if to_df=True.

degrees() dict[str, int]

Get connection count for each selected node.

Returns:

{node_title: degree}.

describe(types: list[str] | None = None, type_search: str | None = None, connections: bool | list[str] | None = None, cypher: bool | list[str] | None = None, fluent: bool | list[str] | None = None, max_pairs: int | None = None, sample_truncate: int | None = 40) str

Return an XML description of this graph for AI agents.

Five independent axes for progressive disclosure:

Node types (types parameter):

  • describe() — Inventory overview with compact type descriptors and connections with edge property names. Adapts to graph scale: small graphs get full inline detail, extreme-scale graphs get a statistical summary with search hints.

  • describe(types=['Field', 'Well']) — Focused detail for specific types with properties, connections, and samples. Each type carries a schema-adapted <example> query anchored on its real identifier property (its id alias, else id) with a concrete sampled value, so an agent copies a query that matches that type’s key shape.

Type search (type_search parameter):

  • describe(type_search='software') — Find types by name (case-insensitive substring match) with neighborhood fan-out. Returns matching types with their connections, plus one layer of connected types. Ideal for exploring large/extreme-scale graphs where listing all types is impractical.

Connections (connections parameter):

  • describe(connections=True) — All connection types with count, source/target node types, and property names.

  • describe(connections=['BELONGS_TO']) — Deep-dive with per-pair counts, property stats with sample values, and sample edges. Use this to discover what data edges carry.

Cypher (cypher parameter):

  • describe(cypher=True) — Compact Cypher reference: all clauses, operators, functions, and procedures with 1-line descriptions.

  • describe(cypher=['cluster', 'MATCH']) — Detailed docs with parameters and examples for specific topics.

Fluent API (fluent parameter):

  • describe(fluent=True) — Compact fluent API reference: all methods grouped by area with signatures and descriptions.

  • describe(fluent=['traverse', 'where', 'spatial']) — Detailed docs with parameters and examples for specific topics.

When type_search, connections, cypher, or fluent is set, only those tracks are returned (no node inventory).

Parameters:
  • types – Node type names for focused detail.

  • type_search – Case-insensitive substring pattern to search type names. Returns matching types with connections + 1 layer of connected types for neighborhood discovery.

  • connections – True for overview, list for deep-dive into specific types.

  • cypher – True for compact reference, list for detailed topic docs.

  • fluent – True for compact reference, list for detailed topic docs.

  • max_pairs – Cap on (src_type, tgt_type) pairs rendered in the describe(connections=['T']) deep-dive. Defaults to 50. Sorted by count desc, so the head covers the dominant relationships; a trailing <more pairs="…" edges="…"/> marker reports the hidden tail. Raise to drill into wide fan-out connection types (e.g. Wikidata P31 has 191k distinct pairs).

  • sample_truncate – Max chars for string values in vals= attributes, sample-node ids/titles, and sample-edge attributes. Defaults to 40. Pass None to disable truncation entirely — useful when you want full titles in an LLM prompt and have context-window budget for it. Has no effect on stored data; only the rendered XML.

Raises:
  • ValueError – If any type/connection/topic is not found.

  • TypeError – If connections, cypher, or fluent has wrong type.

difference(other: KnowledgeGraph) KnowledgeGraph

Keep nodes in self but not in other (set difference).

Returns:

A new KnowledgeGraph with the difference.

disable_columnar() None

Convert columnar properties back to compact per-node storage.

This is the inverse of enable_columnar(). Useful before saving to .kgl format or when columnar storage is no longer needed.

drop_composite_index(node_type: str, properties: list[str]) bool

Remove a composite index. Returns True if it existed.

drop_index(node_type: str, property: str) bool

Remove an index. Returns True if it existed.

drop_range_index(node_type: str, property: str) bool

Remove a range index. Returns True if it existed.

drop_vector_index(node_type: str, text_column: str) bool

Drop the HNSW index for an embedding store (search reverts to exact).

Returns True if an index was dropped, False if none existed.

embed_texts(node_type: str, text_column: str, batch_size: int = 256, show_progress: bool = True, replace: bool = False, mode: str | None = None) dict[str, int]

Embed a text column for all nodes of a given type.

Uses the model registered via set_embedder(). Reads each node’s text_column property, calls model.embed() in batches, and stores the resulting vectors as {text_column}_emb. Nodes with missing or non-string text are skipped.

The store also records, per node, a hash of the embedded text and (when the embedder names it) the model id — so a later embed_texts(mode='changed') can re-embed exactly the nodes whose text changed, and embedding_info() can report provenance.

Shows a tqdm progress bar by default (requires tqdm).

Parameters:
  • node_type – The node type to embed (e.g. 'Article').

  • text_column – The node property containing text to embed.

  • batch_size – Number of texts per model.embed() call (default 256).

  • show_progress – Show a tqdm progress bar (default True). Silently falls back to no bar if tqdm is not installed.

  • replace – Legacy alias for mode. Truemode='all', Falsemode='missing'. Ignored when mode is given.

  • mode – Which nodes to embed — 'missing' (default): only nodes without an embedding; 'changed': nodes missing an embedding or whose text changed since the last embed (via the stored content hash) — the incremental re-embed; 'all': re-embed every node, rebuilding the store fresh.

Returns:

Dict with embedded, skipped, skipped_existing, reembedded_changed, and dimension.

Example:

g.set_embedder(my_model)
g.embed_texts("Article", "summary")
# Embedding Article.summary: 100%|████████| 1000/1000 [00:05<00:00]

# Add/edit articles, then re-embed only what changed:
g.embed_texts("Article", "summary", mode="changed")
embedding(node_type: str, text_column: str, node_id: Any) list[float] | None

Retrieve a single node’s embedding vector.

Parameters:
  • node_type – The node type (e.g. ‘Article’).

  • text_column – Source text column name (e.g. ‘summary’).

  • node_id – The node ID to look up.

Returns:

The embedding vector as a list of floats, or None if not found.

embedding_diagnostics(node_type: str | None = None) list[dict[str, Any]]

Diagnose embedding coverage per (node_type, text_column).

Companion to list_embeddings(). Surfaces three states:

  • "embedded": a store exists and at least one node has the underlying property.

  • "embeddable": nodes have a string-typed property but no embedding store has been created or restored.

  • "store_orphan": a store exists but no node in the current graph has the underlying property — the symptom import_embeddings() warns about when keys mismatch.

Each row also carries a length_stats dict so callers can filter on string-length distribution + cardinality before committing to embed a column. ISO timestamps, status enums, and fully-unique identifiers show up with status="embeddable" but their length_stats distinguishes them from real candidates:

# keep columns averaging ≥ 20 chars and not fully-unique
candidates = [d for d in g.embedding_diagnostics()
              if d["length_stats"]["mean_length"] >= 20
              and d["length_stats"]["distinct_ratio"] < 1.0]
Parameters:

node_type – Optional filter. When set, only that node type is scanned. When None, every type in the graph is scanned — may be expensive on graphs with millions of nodes.

Returns:

node_type, text_column, embedding_key (= f"{text_column}_emb"), nodes_with_property, nodes_embedded, dimension (or None), metric (or None), status, and length_stats with mean_length / max_length / distinct_count / distinct_ratio.

Return type:

List of dicts with

embedding_dim(node_type: str, text_column: str) int | None

The vector dimension of the (node_type, text_column) embedding store, or None if none exists.

A cheap, direct way to detect an embedder/model change without bookkeeping: compare it against your model’s dimension before re-embedding. embed_texts / add_embeddings reject a dimension mismatch (re-embed with replace=True to rebuild at a new dimension).

Example:

if g.embedding_dim("Article", "summary") not in (None, model.dimension):
    g.embed_texts("Article", "summary", replace=True)  # model changed
embedding_info(node_type: str, text_column: str) dict[str, Any] | None

Provenance for the (node_type, text_column) embedding store, or None if no store exists.

Returns a dict with dimension, count (vectors stored), model (the embedder id stamped at embed_texts time, or None for vectors supplied directly via add_embeddings), metric, and hashed (how many vectors carry a source-text hash, used by embed_texts(mode='changed') for change-detection). Detect a model swap or a partially-hashed store without external bookkeeping.

metric is the store’s effective distance metric: the one set via set_embeddings(metric=...) if any, else 'cosine' (the default search applies). It is never None for an existing store — a store with no explicit metric reports 'cosine' (consistent with list_embeddings()).

The model is populated when the embedder exposes a model_id / model_name attribute (or is the built-in fastembed backend).

embeddings(node_type: str, text_column: str) dict[Any, list[float]]
embeddings(text_column: str) dict[Any, list[float]]

Retrieve embeddings for nodes in the current selection.

Parameters:

text_column – Source text column name (e.g. ‘summary’).

Returns:

Dict mapping node IDs to embedding vectors.

enable_columnar() None

Convert node properties to columnar storage.

Properties are moved from per-node storage into per-type column stores, reducing memory usage for homogeneous typed columns (int64, float64, string, etc.). Automatically compacts properties first if not already compacted.

Example:

graph.enable_columnar()
assert graph.is_columnar
enable_disk_mode() None

Convert the graph to disk-backed storage mode.

Enables columnar storage first (if not already), then builds CSR (Compressed Sparse Row) edge arrays on disk. Nodes stay in memory (~40 bytes each), edges are mmap’d from disk.

This reduces memory usage to ~10% of the in-memory graph for edge-heavy graphs. Best called after all data is loaded.

All query methods (Cypher, fluent API, algorithms) work identically in disk mode.

Example:

graph.enable_disk_mode()
exists(node_type: str, unique_id: Any) bool

Return True if a node of node_type with that id exists. O(1).

Uses the same hash index as node() (no scan), and mirrors its id-coercion semantics: ids are integers in every storage mode, so a Python int and the stored id normalize to the same key.

Parameters:
  • node_type – The node type (e.g. 'User').

  • unique_id – The unique ID value.

Returns:

True if a matching node exists, False otherwise.

expand(hops: int | None = None) KnowledgeGraph

Expand the selection by N hops (breadth-first, undirected).

Parameters:

hops – Number of hops to expand. Default 1.

Returns:

A new KnowledgeGraph with the expanded selection.

explain() str

Return a human-readable execution plan for the current query chain.

Example output:

SELECT Person (500 nodes) -> WHERE (42 nodes)
static explain_mcp() str

Return a self-contained XML quickstart for setting up a KGLite MCP server.

Covers the bundled kglite-mcp-server console script (the default on-ramp — no fork required), the YAML manifest for adding custom tools (source_root: for file access, inline Cypher tools, and trust-gated Python hooks), and Claude Desktop / Claude Code registration config.

Example:

print(KnowledgeGraph.explain_mcp())
explore(query: str, max_entities: int = 10, max_depth: int = 2, include_source: bool = True, source_roots: list[str] | None = None) str

One-call codebase exploration over a code-tree graph.

Lexically ranks Function / Class / Interface / Struct / Trait / Protocol / Enum nodes against query (matched against name + signature + docstring), takes the top max_entities, 2-hop traverses CALLS / USES_TYPE / HAS_METHOD / DEFINES / REFERENCES_FN, and returns a markdown report with entry points, a relationship map, and grouped source slices for the entry points.

Designed for the “how does X work in this codebase” question that would otherwise require a chain of grep + read calls. Composes FTS + traversal + source-slicing into a single Rust- side call.

Parameters:
  • query – Free-text topic. Matched against name + signature + docstring. Exact name matches rank highest.

  • max_entities – Top N entry points after ranking (default 10).

  • max_depth – Hops for the neighborhood traversal (default 2).

  • include_source – Whether to include source slices for entry points (default True). Set False for a smaller, faster response when the entity list is all you need.

  • source_roots – Filesystem roots to resolve file_path properties against. Files matched literally are tried first; roots are searched in order. Default: cwd only.

Returns:

A markdown string with ## Query / ## Entry points / ## Related / ## Source sections. Empty queries and graphs with no matching entities return a clear no-match message rather than raising.

export(path: str, format: str | None = None, selection_only: bool | None = None) None

Export the graph to a file.

Supported formats: graphml, gexf, d3/json, csv. Format is inferred from the file extension if not specified.

Parameters:
  • path – Output file path.

  • format – Export format. Default: inferred from extension.

  • selection_only – Export only selected nodes. Default: True if selection exists.

export_csv(path: str, selection_only: bool | None = None, verbose: bool = False) dict[str, Any]

Export to an organized CSV directory tree with blueprint.

Creates a directory structure with one CSV per node type and one per connection type, plus a blueprint.json for round-trip re-import via from_blueprint().

Output structure:

path/
├── nodes/
│   ├── Person.csv
│   ├── Company.csv
│   └── Person/          # sub-nodes nested under parent
│       └── Skill.csv
├── connections/
│   ├── WORKS_AT.csv
│   └── KNOWS.csv
└── blueprint.json

Node CSVs have columns: id, title, then all properties. Connection CSVs: source_id, source_type, target_id, target_type, then edge properties.

Only connections where both endpoints are in the selection are exported.

Parameters:
  • path – Output directory (created if it doesn’t exist).

  • selection_only – Export only selected nodes. Default: True if a selection exists, False otherwise.

  • verbose – Print progress information during export.

Returns:

Summary dict with keys output_dir, nodes (type → count), connections (type → count), files_written.

Example:

graph.export_csv('output/')
graph.select('Person').export_csv('output/', verbose=True)
export_embeddings(path: str, node_types: list[str] | dict[str, list[str]] | None = None) dict[str, int]

Export embeddings to a standalone .kgle file, keyed by node ID.

Parameters:
  • path – Output .kgle file path.

  • node_types

    Optional filter.

    • None (default): export all stores.

    • list[str]: only stores whose node_type is in the list.

    • dict[str, list[str]]: per-type list of text columns to export.

Returns:

Dict with stores (count of stores written) and embeddings (total embedding vectors written).

export_string(format: str, selection_only: bool | None = None) str

Export the graph to a string.

Supported formats: graphml, gexf, d3/json.

Parameters:
  • format – Export format.

  • selection_only – Export only selected nodes.

extend(other: KnowledgeGraph, conflict_handling: str | None = None) dict[str, Any]

Merge another KnowledgeGraph into this one, in place.

A native alternative to round-tripping through CSV export/import when building a graph incrementally from multiple sources (or merging two .kgl files loaded into memory). The other graph is read-only and never mutated. Both graphs must use the default in-memory storage.

Example:

g1 = kglite.load('source_a.kgl')
g2 = kglite.load('source_b.kgl')
report = g1.extend(g2)              # g2 folded into g1 in place
report = g1.extend(g2, 'preserve')  # existing g1 values win

Semantics:

  • Node identity is (node_type, id) — the key the id index uses. id is the canonical integer node id in every storage mode. When a node in other matches an existing node here, the conflict is resolved by conflict_handling (same vocabulary as add_nodes):

    • 'update' (default) — merge properties, other wins on conflicts; title is overwritten.

    • 'replace' — replace all properties and the title with other’s.

    • 'skip' — leave the existing node untouched.

    • 'preserve' — merge properties, existing values win; title kept unless currently null.

    • 'sum' — adds numeric property values on edges; for node properties it acts as update (matches add_nodes / add_connections 'sum' semantics).

  • Secondary labels (multi-label, since 0.10.5) are unioned onto the matched/created node — never removed. Idempotent.

  • Property schemas merge: a property present in other but not here extends this graph’s type schema (the same path add_nodes uses for new columns).

  • Edges dedup on (connection_type, source, target): an edge that already exists here is not duplicated — its properties merge per conflict_handling. Exact-duplicate edges present in both graphs are created once, not twice (mirrors add_connections’ dedup so a merge never silently doubles shared edges).

Scope limits (v1):

  • In-memory only. Both graphs must use the default in-memory storage; storage='mapped' / 'disk' graphs raise an error suggesting the export/import path.

  • Embeddings are NOT merged. If other has any embedding stores a warning is emitted — re-run set_embeddings / add_embeddings after the merge to rebuild them here.

  • Self-extend (g.extend(g)) is a no-op for creation: every node/edge already matches itself, so the result is a property merge against self (a no-op under every mode but 'replace').

  • Locks. Like add_nodes / add_connections, this bulk path does not consult schema_locked / read_only (those gate the Cypher write path only).

Parameters:
  • other – KnowledgeGraph to merge into this one (read-only).

  • conflict_handling – ‘update’ (default), ‘replace’, ‘skip’, ‘preserve’, or ‘sum’.

Returns:

Operation report dict with nodes_created, nodes_updated, nodes_skipped, edges_created, edges_skipped, node_types_merged, connection_types_merged, labels_unioned, processing_time_ms, has_errors, and optionally errors.

find(name: str, node_type: str | None = None, match_type: str | None = None) list[dict[str, Any]]

Find code entities by name, with disambiguation context.

Code-entity search only. find() searches nodes of type Function, Struct, Class, Enum, Trait, Protocol, Interface, Module, or Constant — the types produced by kglite.code_tree. On graphs that don’t contain these types (e.g. a social graph with Person nodes), find() returns an empty list. For general name lookup on other node types, use select(type).where({"name": ...}) or cypher("MATCH (n:Type) WHERE n.name = $n RETURN n", params={"n": ...}).

Parameters:
  • name – Entity name to search for (e.g. "execute").

  • node_type – Optional filter — only search this node type (e.g. "Function", "Struct").

  • match_type – Matching strategy: "exact" (default), "contains" (case-insensitive substring), or "starts_with" (case-insensitive prefix).

Returns:

type, name, qualified_name, file_path, line_number, and optionally signature and visibility.

Return type:

List of dicts with

freeze() FrozenGraph

Take an immutable, concurrently-readable snapshot of the graph.

Returns a FrozenGraph that shares this graph’s data (an O(1) clone — no deep copy) and exposes only read methods. A live KnowledgeGraph is single-owner and raises if a second thread touches it while another mutates it; a FrozenGraph has no mutating method, so any number of threads can run cypher() against the same snapshot in parallel, lock-free.

The snapshot is stable: mutating the source graph afterwards copy-on-writes a fresh copy, leaving the frozen view on the original data — the “build → freeze → share → swap” model for serving concurrent readers while a new snapshot is built in the background.

get_default_max_rows() int | None

Get the current default max rows limit, or None.

get_default_timeout() int | None

Get the current default query timeout in milliseconds, or None.

get_properties(properties: list[str], limit: int | None = None, indices: list[int] | None = None, flatten_single_parent: bool | None = None) list[tuple[Any, Ellipsis]] | dict[str, list[tuple[Any, Ellipsis]]]

Get specific properties for selected nodes.

Without traversal (single parent group), returns a flat list of tuples. After traversal (multiple parent groups), returns {parent_title: [tuples]}.

Parameters:
  • properties – List of property names to retrieve.

  • limit – Maximum number of nodes.

  • indices – Specific node indices.

  • flatten_single_parent – Flatten single-group results to a list. Default True.

Returns:

list[tuple] when flattened, dict[str, list[tuple]] when grouped.

graph_info() dict[str, Any]

Get diagnostic information about graph storage health.

Returns a dictionary with storage metrics useful for deciding when to call vacuum() or reindex().

Returns:

  • node_count: Number of live nodes

  • node_capacity: Upper bound of node indices (includes tombstones)

  • node_tombstones: Number of wasted slots from deletions

  • edge_count: Number of live edges

  • fragmentation_ratio: Ratio of wasted storage (0.0 = clean)

  • type_count: Number of distinct node types

  • property_index_count: Number of single-property indexes

  • composite_index_count: Number of composite indexes

  • columnar_heap_bytes: Heap-resident bytes in columnar stores

  • columnar_is_mapped: Whether any columnar data is file-backed

  • memory_limit: Configured memory limit (None if unset)

  • columnar_total_rows: Total rows in columnar stores (includes orphaned)

  • columnar_live_rows: Rows backed by live nodes

Return type:

dict with keys

Example:

info = graph.graph_info()
if info['fragmentation_ratio'] > 0.3:
    graph.vacuum()
has_composite_index(node_type: str, properties: list[str]) bool

Check if a composite index exists.

has_index(node_type: str, property: str) bool

Check if an index exists.

has_schema() bool

Check if a schema has been defined.

has_vector_index(node_type: str, text_column: str) bool

Whether an HNSW index is currently built over the (node_type, text_column) embedding store.

ids() list[Any]

Return a flat list of ID values from the current selection.

The lightest retrieval method — no dict wrapping.

import_embeddings(path: str) dict[str, int]

Import embeddings from a .kgle file.

Matches embeddings to nodes by (node_type, node_id). Embeddings whose node ID doesn’t exist in the current graph are skipped.

When all embeddings — or whole per-type stores — fail to match, a UserWarning is emitted to surface the silent-drop case. This most commonly indicates the .kgle file was exported from a different graph, or that the node ID schema has drifted (e.g. the code_tree qualified-name format changed across kglite versions).

Parameters:

path – Path to a .kgle file previously created by export_embeddings().

Returns:

  • stores: number of stores actually inserted.

  • imported: total embedding vectors matched to current nodes.

  • skipped: total entries in the file that didn’t match.

  • dropped_stores: number of per-type stores in the file that contained entries but had zero matches (so the store was not inserted).

Return type:

Dict with

index_stats(node_type: str, property: str) dict[str, Any] | None

Get statistics for an index. Returns None if not found.

indexes() list[dict[str, Any]]

Return a unified list of all indexes.

Returns:

  • node_type: the indexed node type

  • property: property name (for equality indexes)

  • properties: list of property names (for composite indexes)

  • type: 'equality' or 'composite'

Return type:

List of dicts, each with

indices() list[int]

Return raw graph indices for selected nodes.

intersection(other: KnowledgeGraph) KnowledgeGraph

Keep only nodes present in both selections (set intersection).

Returns:

A new KnowledgeGraph with only shared nodes.

intersects_geometry(query_wkt: str | Any, geometry_field: str | None = None) KnowledgeGraph

Filter nodes whose geometry intersects a WKT geometry.

Parameters:
  • query_wkt – WKT string or shapely geometry object.

  • geometry_field – Geometry property name. Default 'geometry'.

Returns:

A new KnowledgeGraph with intersecting nodes.

label_pair_counts() list[tuple[str, str, str, int]]

Return the label-pair edge-count cardinality cache.

Lists every (src_type, edge_type, tgt_type) triple present in the graph along with its edge count. Backs the Cypher planner’s 0.9.35 selectivity-aware reorder_match_clauses pass — picking the more-selective driving side on label-skewed patterns by consulting per-triple counts instead of edge-type totals.

Computed lazily: first access walks every edge once (O(E)); subsequent reads return a cached snapshot in O(triples) (typically <100 entries). Edge mutations — Cypher CREATE / DELETE, Python add_connections — invalidate the cache.

Returns:

A list of 4-tuples [(src_type, edge_type, tgt_type, count), ...]. Order is hash-arbitrary; sort the result if a deterministic row order matters.

Example

```python for src, edge, tgt, count in graph.label_pair_counts():

print(f”{src} -[:{edge}]-> {tgt}: {count}”)

```

label_propagation(max_iterations: int | None = None, connection_types: list[str] | None = None, timeout_ms: int | None = None) dict[str, Any]

Detect communities using label propagation.

Parameters:
  • max_iterations – Maximum iterations. Default 100.

  • connection_types – Only consider edges of these types. Default all edge types.

  • timeout_ms – Abort after this many milliseconds, returning partial results.

Returns:

Dict with communities, modularity, and num_communities.

last_report() dict[str, Any]

Get the most recent operation report as a dict.

Returns an empty dict if no operations have been performed.

len() int

Count selected nodes without materialising them.

Much faster than len(collect()). Also available via len(graph).

limit(max_per_group: int) KnowledgeGraph

Limit the number of nodes per parent group.

Parameters:

max_per_group – Maximum number of nodes to keep per group.

Returns:

A new KnowledgeGraph with the limited selection.

list_composite_indexes() list[dict[str, Any]]

List all composite indexes.

list_embeddings() list[dict[str, Any]]

List all embedding stores in the graph.

Returns:

List of dicts with node_type, text_column, dimension, count.

list_indexes() list[dict[str, str]]

List all indexes. Each dict has type and property.

load_ntriples(path: str, *, predicates: list[str] | None = None, languages: list[str] | None = None, node_types: dict[str, str] | None = None, predicate_labels: dict[str, str] | None = None, max_entities: int | None = None, max_triples: int | None = None, verbose: bool = False, progress: Callable[[dict], None] | None = None) dict

Load an N-Triples file into the graph.

Streams the file (supports .bz2, .gz, or plain .nt) and converts RDF triples into a property graph. Designed for Wikidata truthy dumps but works with any N-Triples file.

RDF → property graph mapping:

  • Each unique Q-entity subject becomes a node.

  • rdfs:label (language-filtered) → node title.

  • schema:descriptiondescription property.

  • prop/direct/P* with a Q-entity object → edge.

  • prop/direct/P* with a literal object → node property.

  • P31 (instance of) determines the node type via node_types.

Parameters:
  • path – Path to the N-Triples file (.nt, .nt.bz2, .nt.gz).

  • predicates – Wikidata P-codes to import (e.g. ["P31", "P279"]). None imports all predicates. Labels and descriptions are always imported regardless of this filter.

  • languages – Language codes for label/description literals (e.g. ["en"]). None keeps all languages.

  • node_types – Maps a P31 target Q-code to a human-readable node type name (e.g. {"Q5": "Person", "Q6256": "Country"}). Entities whose P31 value is not in this map use the raw Q-code as their type. Entities without P31 get type "Entity".

  • predicate_labels – Maps P-codes to human-readable edge/property names (e.g. {"P31": "instance_of", "P17": "country"}). Unmapped predicates use the raw P-code.

  • max_entities – Stop after creating this many nodes. Useful for exploratory loading of large dumps.

  • max_triples – Stop after scanning this many triples (entity and non-entity lines alike). Applied alongside max_entities; whichever fires first wins. Useful for benchmarking the bz2/gz/plain decompression + parser pipeline at a fixed wall-time-friendly slice.

  • verbose – Print progress to stderr every 5M triples.

  • progress – Optional callable receiving structured per-phase events. The callback is invoked with a single dict whose "kind" is "start", "update", or "complete" and whose "phase" is one of "phase1", "phase1b", "phase2", "phase3", "finalising". See kglite.progress.TqdmBuildProgress for a tqdm-backed reporter. Errors raised by the callback are swallowed.

Returns:

A dict with load statistics:

{"entities": int, "edges": int, "edges_skipped": int,
 "triples_scanned": int, "seconds": float}

Example:

graph = KnowledgeGraph()
stats = graph.load_ntriples(
    "latest-truthy.nt.bz2",
    predicates=["P31", "P279", "P17", "P106"],
    languages=["en"],
    node_types={"Q5": "Person", "Q6256": "Country"},
    predicate_labels={"P31": "instance_of", "P17": "country"},
    max_entities=1_000_000,
    verbose=True,
)
lock_schema() KnowledgeGraph

Lock the schema: future Cypher mutations must conform to current types.

When locked, CREATE, SET, and MERGE operations are validated against the graph’s known node types, connection types, and property types. Invalid writes return descriptive errors with ‘did you mean?’ suggestions.

Returns:

Self for method chaining.

Example:

graph.lock_schema()
graph.cypher("CREATE (p:Typo {name: 'x'})")  # raises RuntimeError
louvain_communities(weight_property: str | None = None, resolution: float | None = None, connection_types: list[str] | None = None, timeout_ms: int | None = None) dict[str, Any]

Detect communities using the Louvain algorithm.

Parameters:
  • weight_property – Edge property to use as weight. Default all edges weight 1.0.

  • resolution – Resolution parameter (higher = more communities). Default 1.0.

  • connection_types – Only consider edges of these types. Default all edge types.

  • timeout_ms – Abort after this many milliseconds, returning partial results.

Returns:

Dict with communities (dict of community_id to member list), modularity, and num_communities.

match_pattern(pattern: str, max_matches: int | None = None) list[dict[str, Any]]

Match a Cypher-like pattern against the graph.

Supports node patterns (a:Type {prop: val}), directed edges -[:TYPE]->, <-[:TYPE]-, and undirected -[:TYPE]-.

Parameters:
  • pattern – Pattern string, e.g. '(a:Person)-[:KNOWS]->(b:Person)'.

  • max_matches – Maximum results to return.

Returns:

List of match dicts with variable bindings.

near_point(center_lat: float, center_lon: float, max_distance: float, lat_field: str | None = None, lon_field: str | None = None) KnowledgeGraph

Filter nodes within a distance (in degrees) of a point.

Parameters:
  • center_lat – Center latitude.

  • center_lon – Center longitude.

  • max_distance – Maximum distance in degrees.

  • lat_field – Latitude property name. Default 'latitude'.

  • lon_field – Longitude property name. Default 'longitude'.

Returns:

A new KnowledgeGraph with nearby nodes.

near_point_m(center_lat: float, center_lon: float, max_distance_m: float, lat_field: str | None = None, lon_field: str | None = None) KnowledgeGraph

Filter nodes within a distance (in meters) using geodesic calculation.

Uses WGS84 ellipsoid for accurate Earth-surface distances. Falls back to geometry centroid when lat/lon fields are missing but a WKT geometry is configured via set_spatial.

Parameters:
  • center_lat – Center latitude.

  • center_lon – Center longitude.

  • max_distance_m – Maximum distance in meters.

  • lat_field – Latitude property name. Default from spatial config or 'latitude'.

  • lon_field – Longitude property name. Default from spatial config or 'longitude'.

Returns:

A new KnowledgeGraph with nearby nodes.

neighbors_schema(node_type: str) dict[str, list[dict[str, Any]]]

Return connection topology for a node type.

Parameters:

node_type – The node type to inspect.

Returns:

  • outgoing: list of {connection_type, target_type, count}

  • incoming: list of {connection_type, source_type, count}

Return type:

Dict with

Raises:

KeyError – If node_type does not exist.

node(node_type: str, node_id: Any) dict[str, Any] | None

Look up a single node by type and ID. O(1) via hash index.

Parameters:
  • node_type – The node type (e.g. 'User').

  • node_id – The unique ID value.

Returns:

Node property dict, or None if not found.

node_type_counts() dict[str, int]

Get node counts per type without materialising nodes.

Returns:

Dict mapping node type name to count.

offset(n: int) KnowledgeGraph

Skip the first n nodes per parent group (pagination).

Combine with limit() for pagination: graph.sort('name').offset(20).limit(10)

Parameters:

n – Number of nodes to skip.

Returns:

A new KnowledgeGraph with the offset selection.

operation_index() int

Get the sequential index of the last operation.

pagerank(damping_factor: float | None = None, max_iterations: int | None = None, tolerance: float | None = None, connection_types: str | list[str] | None = None, top_k: int | None = None, as_dict: bool | None = None, timeout_ms: int | None = None, to_df: bool | None = None) ResultView | dict[Any, float] | pandas.DataFrame

Calculate PageRank centrality.

Parameters:
  • damping_factor – Probability of following a link. Default 0.85.

  • max_iterations – Maximum iterations. Default 100.

  • tolerance – Convergence threshold. Default 1e-6.

  • connection_types – Only traverse these relationship types (str or list).

  • top_k – Return only the top K nodes.

  • as_dict – Return {id: score} dict instead of list of dicts.

  • timeout_ms – Abort after this many milliseconds, returning partial results.

  • to_df – Return a pandas DataFrame with columns type, title, id, score.

Returns:

List of dicts with type, title, id, score. Or a DataFrame if to_df=True.

properties(node_type: str, max_values: int = 20) dict[str, dict[str, Any]]

Return property statistics for a node type.

Only properties that exist on at least one node are included.

Parameters:
  • node_type – The node type to inspect.

  • max_values – Include values list when unique count <= this threshold. Set to 0 to never include values. Default: 20.

Returns:

  • type: type string (e.g. 'str', 'int', 'float')

  • non_null: count of non-null values

  • unique: count of distinct values (a lower bound when approx is True)

  • values: (optional) sorted list of values when unique count <= max_values

  • approx: True when unique/values are not exhaustive — the type was sampled (only huge, Wikidata-scale types) or the distinct-value set hit its cap. Types at or below ~200k nodes are scanned in full and report exact stats (approx False).

Return type:

Dict mapping property name to stats dict with keys

Raises:

KeyError – If node_type does not exist.

purge_provisional() dict[str, Any]

Delete provisional stub nodes that were never promoted.

When an edge is loaded against a node that doesn’t exist, the node is auto-vivified as a provisional stub (carrying _provisional) so the edge isn’t lost. A later load of the real node row clears the marker. purge_provisional() deletes whatever is still marked — genuinely dangling references — along with their incident edges.

Important: This resets the current selection since node indices change. Call this between query chains, not in the middle of one.

Returns:

  • nodes_purged: Number of provisional stub nodes deleted

  • edges_removed: Number of incident edges removed with them

Return type:

dict with keys

Example:

result = graph.purge_provisional()
print(f"Dropped {result['nodes_purged']} dangling stubs")
read_only(enabled: bool | None = None) bool

Set or query read-only mode for the Cypher layer.

When enabled, all Cypher mutation queries (CREATE, SET, DELETE, REMOVE, MERGE) are rejected, and describe() omits mutation docs.

Parameters:

enabled – If True, enable read-only mode. If False, disable. If omitted, return the current state without changing it.

Returns:

The current read-only state (after applying the change, if any).

Example:

graph.read_only(True)   # lock the graph
graph.read_only()       # -> True
graph.read_only(False)  # unlock
rebuild_caches() None

Force recomputation of internal caches (edge type counts, etc.).

Call once after bulk mutations to warm the cache before save() or describe(). The cache is persisted by save() and restored by load(), so this only needs to be called once after building or mutating a graph.

Example:

g = KnowledgeGraph()
g.add_nodes(...)
g.add_connections(...)
g.rebuild_caches()   # one-time O(E) pass
g.save("graph.kgl")  # persists warm cache
rebuild_indexes() int

Rebuild all indexes. Returns the number of indexes rebuilt.

reindex() None

Rebuild all indexes from the current graph state.

Reconstructs type_indices, property_indices, and composite_indices by scanning all live nodes. Clears lazy caches (id_indices, connection_types) so they rebuild on next access.

Use after bulk mutations (especially Cypher DELETE/REMOVE) to ensure index consistency.

Example:

graph.reindex()
remove_embeddings(node_type: str, text_column: str) None

Remove an embedding store.

Parameters:
  • node_type – The node type.

  • text_column – Source text column name (e.g. 'summary').

remove_label(node_type: str, ids: list[Any], label: str) dict[str, int]

Remove a secondary label from a batch of nodes by id.

Errors if label is the primary type — use SET n.type to retype a node instead.

Parameters:
  • node_type – Primary type of the nodes.

  • ids – Node ids.

  • label – Secondary label to remove.

Returns:

Dict with removed and skipped (unknown ids, or label not present on the node).

replace_connections(data: pandas.DataFrame | None, connection_type: str, source_type: str, source_id_field: str, target_type: str, target_id_field: str, source_title_field: str | None = None, target_title_field: str | None = None, columns: list[str] | None = None, skip_columns: list[str] | None = None, conflict_handling: str | None = None, column_types: dict[str, str] | None = None, query: str | None = None, extra_properties: dict[str, Any] | None = None) dict[str, Any]

Replace a node’s outgoing edges of a given type, then add new ones — an atomic edge upsert.

Unlike add_connections() (add-only), this prunes first: for every source node present in data (or the query result), its existing edges of connection_type are removed, then the edges the input describes are added. Edges from sources not in the input, and edges of other types from the same sources, are left untouched. The prune and add happen in one call, so there is no clear-then-add window that could leave a node edgeless if a separate re-add step failed.

Use it to re-sync a derived edge set idempotently — “the current MENTIONS of exactly these documents is this list”:

# First sync: doc 1 -> [A, B]
graph.replace_connections(df_ab, 'MENTIONS', 'Doc', 'doc', 'Entity', 'ent')
# Re-sync doc 1 -> [B, C]: the stale 1->A edge is pruned, 1->C added.
graph.replace_connections(df_bc, 'MENTIONS', 'Doc', 'doc', 'Entity', 'ent')

Accepts every argument add_connections() does (including query mode and extra_properties), with identical semantics; only the prune-first behaviour differs.

Parameters:
  • data – DataFrame containing edge data, or None when using query.

  • connection_type – Edge type to replace (e.g. 'MENTIONS').

  • source_type – Node type of source nodes.

  • source_id_field – Column with source node IDs.

  • target_type – Node type of target nodes.

  • target_id_field – Column with target node IDs.

  • source_title_field – Optional title column for source nodes.

  • target_title_field – Optional title column for target nodes.

  • columns – Whitelist of property columns to include (data mode only).

  • skip_columns – Columns to exclude (data mode only).

  • conflict_handling'update' (default), 'replace', 'skip', 'preserve', or 'sum'.

  • column_types – Override column dtypes (data mode only).

  • query – Cypher query string (alternative to data). Must be read-only.

  • extra_properties – Static properties stamped onto every edge (query mode only).

Returns:

Operation report dict with connections_created, connections_skipped, etc.

report_history() list[dict[str, Any]]

Get all operation reports as a list of dicts.

sample(node_type: str, n: int = 5) ResultView
sample(n: int = 5) ResultView
save(path: str | None = None, *, fsync: bool = True) None

Serialise the graph to disk, atomically and durably.

For default/mapped modes: saves to a .kgl binary file. For disk mode: saves to a directory containing CSR files and compressed node/edge data. The directory IS the saved graph.

Crash-safety (default/mapped modes). The file is written to a sibling temp file and then atomically renamed over the target, so a crash mid-write can never leave a torn/truncated .kgl — a reader always sees either the previous file or the complete new one. With fsync=True (default) the file and its directory are flushed to physical storage before returning, so a committed save survives an OS/power crash. The temp name is unique per process, so two processes saving the same path won’t corrupt each other’s in-flight write (last rename wins, cleanly — keep one writer per file).

Uses columnar storage internally for efficient compression and larger-than-RAM loading. If the graph is not already in columnar mode, save() enables it automatically (the graph stays columnar after the call).

Load it back with kglite.load() (accepts both files and directories).

Parameters:
  • path – Output file path (typically *.kgl). May be omitted if the graph was opened via kglite.open() or kglite.load(), in which case it defaults to that origin file. Passing a path updates the remembered target (“save as”). Raises ValueError if omitted and there is no remembered path.

  • fsync – When True (default), flush the file and its parent directory to disk before returning (durable against an OS/power crash). Set False to skip the flush for speed — the write is still atomic (temp + rename, no torn file), just not guaranteed flushed to physical media when the call returns.

save_subset(path: str) None

Save the current selection as an independent subgraph file.

Equivalent to kg.to_subgraph().save(path) in a single call. Output is a v3 binary file that reloads via kglite.load(path) (or load(path, storage='disk') for disk mode). All edges between selected nodes are included; node and edge properties round-trip byte-for-byte.

Parameters:

path – Destination path for the subgraph file.

Example

>>> kg.select("Article").expand(hops=1, type="AUTHORED_BY").save_subset(
...     "articles_with_authors.kgl"
... )
schema() dict[str, Any]

Return a full schema overview of the graph.

Returns:

  • node_types: {type_name: {count, properties: {name: type_str}}}

  • connection_types: {conn_name: {count, source_types: list, target_types: list}}

  • indexes: list of "Type.property" strings

  • node_count: total nodes

  • edge_count: total edges

Return type:

Dict with keys

Note

Scans all edges once (O(m)) to compute accurate connection type stats.

schema_definition() dict[str, Any] | None

Get the current schema definition as a dict, or None.

schema_text() str

Return a text summary of the graph schema (node types, connections).

search(text: str, *, property: str = 'title', limit: int = 10) list[dict[str, Any]]

Find nodes matching text on property (default ‘title’).

Tries exact match first, then prefix match. Returns up to limit results as dicts with id (node index), type, title, and id_value (the node’s id-field value, e.g. a Wikidata Q-number).

Requires create_global_index(property) to have been run on a disk-backed graph. Returns an empty list otherwise.

Example:

graph.create_global_index('label')
hits = graph.search('Norway')
# [{'id': 12345, 'type': 'country', 'title': 'Norway',
#   'id_value': 'Q20'}]
search_text(text_column: str, query: str, top_k: int = 10, metric: str = 'cosine', to_df: bool = False, returning: list[str] | None = None, exact: bool = False) list[dict[str, Any]] | pandas.DataFrame

Search embeddings using a text query.

Uses the model registered via set_embedder() to embed the query, then performs vector search within the current selection. Refer to the text column name (e.g. "summary"); the graph resolves it to "summary_emb" internally.

Parameters:
  • text_column – Text column whose embeddings to search (e.g. 'summary').

  • query – The text query to search for.

  • top_k – Number of results (default 10).

  • metric'cosine' (default), 'dot_product', 'euclidean', or 'poincare'.

  • to_df – If True, return a pandas DataFrame.

  • returning – Optional field projection — see vector_search(). Omitted → full hit (all properties); given → id + score + the named fields only.

  • exact – Force an exact brute-force scan even when an HNSW index exists — see vector_search() and build_vector_index().

Returns:

Same format as vector_search() — list of dicts or DataFrame.

Example:

results = g.select("Article").search_text(
    "summary", "find AI articles", top_k=10
)
select(node_type: str, sort: str | list[tuple[str, bool]] | None = None, limit: int | None = None, temporal: bool | None = None, include_secondary: bool = False) KnowledgeGraph

Select all nodes of a given type.

When a temporal config exists for this node type (via set_temporal()), nodes are auto-filtered to those valid at the reference date (today or date() context). Pass temporal=False to include all nodes.

Parameters:
  • node_type – The node type to select (e.g. 'Person').

  • sort – Optional sort spec — a property name or list of (field, ascending) tuples.

  • limit – Limit the number of selected nodes.

  • temporal – Override temporal filtering. None = auto (filter if configured), False = disable, True = require (error if not configured).

  • include_secondary – When True, also select nodes that carry node_type as a secondary label (added via add_label()), not only nodes whose primary type is node_type — the fluent equivalent of Cypher MATCH (n:node_type). Default False preserves primary-type-only selection. On a graph with no secondary labels the two are identical.

Returns:

A new KnowledgeGraph with the filtered selection.

selection() str

Return a text summary of the current selection state.

session() Session

Seed a thread-safe, shareable Session from this graph.

Unlike a live KnowledgeGraph — which is single-owner and trips a borrow guard when shared across threads mid-mutation — a Session exposes only &self methods with synchronisation in an internal lock: concurrent cypher() reads run lock-free, and execute() writes serialise behind the lock with copy-on-write + atomic swap.

The Session is an independent owner: it shares this graph’s data at creation (O(1), no copy), but once either side mutates, copy-on-write forks them and they no longer track each other. The intended model is “build / load with a KnowledgeGraph, then .session() and serve every thread through the Session” — don’t keep mutating the original graph after handing out a Session.

set_auto_vacuum(threshold: float | None) None

Configure automatic vacuum after DELETE operations.

When enabled, the graph automatically compacts itself after Cypher DELETE operations if the fragmentation ratio exceeds the threshold and there are more than 100 tombstones.

Parameters:

threshold – A float between 0.0 and 1.0, or None to disable. Default is 0.3 (30% fragmentation triggers vacuum).

Example:

graph.set_auto_vacuum(0.2)   # more aggressive — vacuum at 20%
graph.set_auto_vacuum(None)  # disable auto-vacuum
graph.set_auto_vacuum(0.3)   # restore default
set_default_max_rows(max_rows: int | None = None) None

Set a default max rows limit for all cypher() calls.

Queries producing more intermediate rows than this will raise an error. Pass None to disable (default). Per-query max_rows overrides this.

set_default_timeout(timeout_ms: int | None = None) None

Set a default query timeout (milliseconds) for all cypher() calls.

  • None (default): fall through to the built-in default of 180_000 ms (3 min) for every storage mode.

  • 0: disable the deadline for every query unless per-call timeout_ms overrides.

  • Positive integer: use as the default.

Per-query timeout_ms always overrides this setting.

set_embedder(model: EmbeddingModel | None) None

Register or unbind an embedding model on the graph.

Pass a model object to register; pass None to unbind the currently-registered embedder.

After registering, embed_texts() and search_text() use the registered model automatically. The model is not serialized — call set_embedder() again after deserializing.

If the model has optional load() / unload() methods, they are called automatically around each embedding operation.

Parameters:

model – An embedding model with dimension and embed() — see EmbeddingModel. Or None to unbind.

Example:

g.set_embedder(my_model)
g.set_embedder(None)  # unbind
set_embeddings(node_type: str, text_column: str, embeddings: dict[Any, list[float]], metric: str | None = None) dict[str, int]

Store embeddings for nodes of the given type. Replaces any existing store for (node_type, "{text_column}_emb").

Embeddings are stored separately from regular node properties and are invisible to collect(), to_df(), and other property-based APIs. The embedding store key is auto-derived as {text_column}_emb.

Validates that text_column exists as a property on the node type (builtins id, title, type are always accepted).

For incremental ingest where you want to add to an existing store without a read-merge-write round-trip, use add_embeddings().

Parameters:
  • node_type – The node type (e.g. 'Article').

  • text_column – Source text column name (e.g. 'summary').

  • embeddings – Dict mapping node IDs to embedding vectors.

  • metric – Default distance metric for this store. Used when no metric is specified at query time. 'cosine' (default), 'dot_product', 'euclidean', or 'poincare'. Persisted with save().

Returns:

Dict with embeddings_stored, dimension, and skipped.

set_instructions(text: str, *, channel: str | None = None) KnowledgeGraph

Set free-text instructions/briefing rendered verbatim at the top of describe() — so an agent that opens the graph cold reads it first.

Unlike sample values in describe(), this text is shown in full (no truncation). It persists in the .kgl. Pass empty text to clear.

Parameters:
  • text – The instructions (plain text / markdown). Shown verbatim.

  • channel – Reserved for per-audience instructions; None (default) sets the single graph-level slot.

Returns:

Self (chainable).

set_memory_limit(limit_bytes: int | None, spill_dir: str | None = None) None

Configure automatic memory-pressure spill for columnar storage.

When a memory limit is set, enable_columnar() will automatically spill the largest column stores to temporary files on disk when total heap usage exceeds the limit.

Parameters:
  • limit_bytes – Maximum heap bytes for columnar data, or None to disable the limit.

  • spill_dir – Directory for spill files. Defaults to system temp dir.

Example:

graph.set_memory_limit(500_000_000)  # 500 MB limit
graph.enable_columnar()  # auto-spills if over limit
graph.set_memory_limit(None)  # disable limit
set_parent_type(node_type: str, parent_type: str) None

Declare a node type as a supporting child of a parent type.

Supporting types are hidden from the describe() inventory and instead appear in the <supporting> section when the parent type is inspected. Their capabilities (timeseries, spatial, etc.) bubble up to the parent descriptor.

Parameters:
  • node_type – The supporting (child) node type.

  • parent_type – The core (parent) node type.

Raises:

ValueError – If either type does not exist in the graph.

Example:

graph.set_parent_type('ProductionProfile', 'Field')
graph.set_parent_type('FieldReserves', 'Field')
set_spatial(node_type: str, *, location: tuple[str, str] | None = None, geometry: str | None = None, points: dict[str, tuple[str, str]] | None = None, shapes: dict[str, str] | None = None) None

Configure spatial properties for a node type.

Parameters:
  • node_type – The node type to configure.

  • location – Primary lat/lon pair as (lat_field, lon_field). At most one per type.

  • geometry – Primary WKT geometry field name. At most one per type.

  • points – Named lat/lon points as {name: (lat_field, lon_field)}.

  • shapes – Named WKT shape fields as {name: field_name}.

set_temporal(type_name: str, valid_from: str, valid_to: str) None

Configure temporal validity for a node type or connection type.

After configuration, select() auto-filters temporal nodes and traverse() auto-filters temporal connections to “current” (today or the date() context).

Auto-detects whether type_name is a node type or connection type.

Parameters:
  • type_name – Node type (e.g. 'FieldStatus') or connection type (e.g. 'HAS_LICENSEE').

  • valid_from – Property name holding the start date.

  • valid_to – Property name holding the end date.

Raises:

ValueError – If type_name is not a known node or connection type.

set_time_index(node_id: Any, keys: list[str] | list[list[int]]) None

Set the sorted time index for a specific node.

If the node already has a timeseries, this replaces its time index and clears all channels.

Parameters:
  • node_id – The node’s unique ID.

  • keys – Sorted list of date strings (e.g. ['2020-01', '2020-02']) or composite integer keys for backwards compat (e.g. [[2020, 1], [2020, 2]]).

set_timeseries(node_type: str, *, resolution: str, channels: list[str] | None = None, units: dict[str, str] | None = None, bin_type: str | None = None) None

Configure timeseries metadata for a node type.

Parameters:
  • node_type – The node type to configure.

  • resolution – Time granularity — 'year', 'month', or 'day'. Determines key depth (year=1, month=2, day=3).

  • channels – Optional list of known channel names.

  • units – Optional map of channel name to unit string, e.g. {'oil': 'MSm3', 'temperature': '°C'}.

  • bin_type – What values represent — 'total', 'mean', or 'sample'. None if unspecified.

shortest_path(source_type: str, source_id: Any, target_type: str, target_id: Any, connection_types: list[str] | None = None, via_types: list[str] | None = None, weight_property: str | None = None, timeout_ms: int | None = None) dict[str, Any] | None

Find the shortest path between two nodes.

Parameters:
  • source_type – Source node type.

  • source_id – Source node ID.

  • target_type – Target node type.

  • target_id – Target node ID.

  • connection_types – Only traverse edges of these types. Default all.

  • via_types – Only traverse through nodes of these types. Default all.

  • weight_property – Edge property to use as cost. When set, the search uses Dijkstra and minimises total weight; when None, BFS minimises hop count. Edges missing the property fall back to weight 1.0 (matches Louvain’s weighted-adjacency convention). Negative weights cause the path to be reported as missing.

  • timeout_ms – Abort after this many milliseconds and return None.

Returns:

Dict with path (list of node info dicts), connections (list of edge types), and length (hop count). When weight_property is set, also includes weight (sum of edge weights). None if no path exists or timeout is reached.

shortest_path_ids(source_type: str, source_id: Any, target_type: str, target_id: Any, connection_types: list[str] | None = None, via_types: list[str] | None = None, timeout_ms: int | None = None) list[Any] | None

Get node IDs along the shortest path.

Parameters:
  • connection_types – Only traverse edges of these types. Default all.

  • via_types – Only traverse through nodes of these types. Default all.

  • timeout_ms – Abort after this many milliseconds and return None.

Returns:

List of node IDs, or None if no path exists or timeout is reached.

shortest_path_indices(source_type: str, source_id: Any, target_type: str, target_id: Any, connection_types: list[str] | None = None, via_types: list[str] | None = None, timeout_ms: int | None = None) list[int] | None

Get raw graph indices along the shortest path.

Fastest path query — no node data lookup.

Parameters:
  • connection_types – Only traverse edges of these types. Default all.

  • via_types – Only traverse through nodes of these types. Default all.

  • timeout_ms – Abort after this many milliseconds and return None.

Returns:

List of integer indices, or None if no path exists or timeout is reached.

shortest_path_length(source_type: str, source_id: Any, target_type: str, target_id: Any, weight_property: str | None = None) int | float | None

Get just the cost of the shortest path.

Faster than shortest_path() when you only need the distance. Does not support connection_types or via_types filtering.

Parameters:

weight_property – When set, uses Dijkstra and returns total weight (float). When None, BFS returns hop count (int).

Returns:

Hop count (int) or total weight (float), or None if no path exists.

show(columns: list[str] | None = None, limit: int = 200) str

Display selected nodes with specific properties in a compact format.

Single level (no traversals): one node per line as Type(val1, val2). Multi-level (after traverse): walks the full chain as Type1(vals) -> Type2(vals) -> Type3(vals).

Parameters:
  • columns – Property names to include. Default ["id", "title"].

  • limit – Maximum output lines. Default 200.

Example:

print(graph.select("Discovery").show(["id", "title"]))
# Discovery(123, Johan Sverdrup)

print(graph.select("Discovery")
    .traverse("IN_FIELD")
    .traverse("DISCOVERY_WELLBORE")
    .show(["id"]))
# Discovery(123) -> Field(456) -> Wellbore(789)
sort(sort: str | list[tuple[str, bool]], ascending: bool | None = None) KnowledgeGraph

Sort the current selection.

Parameters:
  • sort – Property name (string) or list of (field, ascending) tuples.

  • ascending – Direction when sort is a single string. Default True.

Returns:

A new KnowledgeGraph with the sorted selection.

source(name: str, node_type: str | None = None) dict[str, Any]
source(name: list[str], node_type: str | None = None) list[dict[str, Any]]
spatial(node_type: str | None = None) dict[str, Any] | None

Get spatial configuration for a node type or all types.

Parameters:

node_type – If given, return config for this type only. Otherwise return all.

Returns:

Dict with spatial config, or None if not configured.

statistics(property: str, level_index: int | None = None, group_by: str | None = None) Any

Compute descriptive statistics for a numeric property.

Returns per-parent stats including count, mean, std, min, max, sum.

Parameters:
  • property – Numeric property name.

  • level_index – Target level in the hierarchy.

  • group_by – Group results by this property instead of by parent. Returns {group_value: {count, sum, mean, min, max, std}}.

subgraph_stats() dict[str, Any]

Get statistics about the subgraph that would be extracted.

Returns:

Dict with node_count, edge_count, node_types, connection_types.

symmetric_difference(other: KnowledgeGraph) KnowledgeGraph

Keep nodes in exactly one of the selections (symmetric difference).

Returns:

A new KnowledgeGraph with nodes exclusive to each side.

time_index(node_id: Any) list[str] | None

Get the time index for a node as ISO date strings, or None.

timeseries(node_id: Any, channel: str | None = None, start: str | None = None, end: str | None = None) dict[str, Any] | None

Extract timeseries data for a node.

If channel is given, returns {'keys': [...], 'values': [...]}. Otherwise returns {'keys': [...], 'channels': {'name': [...], ...}}.

Parameters:
  • node_id – The node’s unique ID.

  • channel – Optional channel name to extract.

  • start – Optional range start as date string (e.g. '2020', '2020-2').

  • end – Optional range end as date string.

Returns:

Dict with keys and channel data, or None if no timeseries.

timeseries_config(node_type: str | None = None) dict[str, Any] | None

Get timeseries configuration for a node type or all types.

Returns a dict with resolution, channels, units, bin_type.

titles(limit: int | None = None, indices: list[int] | None = None, flatten_single_parent: bool | None = None) list[str] | dict[str, list[str]]

Get titles of selected nodes.

Without traversal (single parent group), returns a flat list of titles. After traversal (multiple parent groups), returns {parent_title: [titles]}.

Parameters:

flatten_single_parent – Flatten single-group results to a list. Default True.

Returns:

list[str] when flattened, dict[str, list[str]] when grouped.

to_bytes() bytes

Serialise the in-memory graph to a .kgl byte buffer.

Returns the same bytes save() writes to disk, so a caller can own the write — push to object storage, a pipe, a checksum, or a custom atomic-write routine — instead of being limited to a filesystem path. Round-trips through kglite.from_bytes().

Default/mapped modes only: a disk-mode graph is a directory, not a single byte stream, so this raises ValueError for disk graphs (use save('dir/') there).

to_df(*, include_type: bool = True, include_id: bool = True) pandas.DataFrame

Export current selection as a pandas DataFrame.

Each node becomes a row with columns for title, type, id, and all properties. Missing properties across different node types become None.

Parameters:
  • include_type – Include type column. Default True.

  • include_id – Include id column. Default True.

Returns:

DataFrame with one row per selected node.

to_networkx() Any

Convert the graph to a networkx.MultiDiGraph.

KGLite is a directed multigraph with typed nodes and edges, so MultiDiGraph is the lossless target. Each node’s id is the networkx node key; node_type, title and every property are attached as node attributes. Each edge’s connection_type is the networkx edge key (so parallel edges of different types between the same pair stay distinct) and is also stored as the connection_type edge attribute alongside every edge property.

The inverse is kglite.from_networkx().

Requires the networkx package: pip install networkx.

Returns:

A networkx.MultiDiGraph mirroring the full graph.

Note

v1 always exports the full graph; the active selection is ignored. A future revision may honour selections.

Example:

import networkx as nx

nxg = graph.to_networkx()
scores = nx.pagerank(nxg)
to_str(limit: int = 50) str

Format the current selection as a human-readable string.

Each node is printed as a block with [Type] title (id: x) header and indented properties, one per line.

Parameters:

limit – Maximum number of nodes to show. Default 50.

to_subgraph() KnowledgeGraph

Extract selected nodes into a new independent graph.

The new graph contains only selected nodes and the edges between them.

to_text() str

Deterministic, human-readable text projection of the whole graph.

Nodes grouped by type and sorted by id; edges sorted by endpoints — so the output is stable across insert order and across save/load. This is the canonical form behind the .kgl git textconv diff filter (also kglite export-text <file> from the CLI), making git diff of two .kgl snapshots show real content changes. Reserved provenance keys (updated_at/git_sha) are omitted so per-write metadata churn doesn’t swamp the diff.

Returns:

The text projection.

toc(file_path: str) dict[str, Any]

Get a table of contents for a file — all code entities defined in it.

Returns entities sorted by line number with a type summary.

Parameters:

file_path – Path of the file (the File node’s id/path).

Returns:

Dict with "file" (path), "entities" (list of entity dicts sorted by line_number, each with type, name, qualified_name, line_number, end_line, and optionally signature), and "summary" (dict of type name to count).

traverse(connection_type: str, level_index: int | None = None, direction: str | None = None, filter_target: dict[str, Any] | None = None, filter_connection: dict[str, Any] | None = None, sort_target: str | list[tuple[str, bool]] | None = None, limit: int | None = None, new_level: bool | None = None, at: str | None = None, during: tuple[str, str] | None = None, temporal: bool | None = None, target_type: str | list[str] | None = None, where: dict[str, Any] | None = None, where_connection: dict[str, Any] | None = None) KnowledgeGraph

Traverse connections to discover related nodes by following graph edges.

For spatial, semantic, or clustering operations, use compare() instead.

Parameters:
  • connection_type – Edge type to follow (e.g. 'HAS_LICENSEE').

  • direction'outgoing', 'incoming', or None (both).

  • target_type – Filter targets to specific node type(s). Accepts a string or list of strings. Useful when a connection type connects to multiple node types.

  • where – Property conditions for target nodes — same operators as .where() ('>', 'contains', 'in', etc.).

  • where_connection – Property conditions for edge properties.

  • sort_target – Sort targets per source. Field name or [(field, ascending)] list.

  • limit – Max target nodes per source.

  • at – Temporal point-in-time filter (e.g. '2005').

  • during – Temporal range filter (e.g. ('2000', '2010')).

  • temporal – Override temporal filtering. False = disable.

  • level_index – Source level in the hierarchy (advanced).

  • new_level – Add targets as new hierarchy level. Default True.

  • filter_target – Alias for where.

  • filter_connection – Alias for where_connection.

Returns:

A new KnowledgeGraph with traversal results selected.

Examples:

# Follow edges
graph.select('Field').traverse('HAS_LICENSEE')

# Filter to specific target type
graph.select('Field').traverse('OF_FIELD', direction='incoming',
    target_type='ProductionProfile')

# Multiple target types
graph.select('Field').traverse('OF_FIELD', direction='incoming',
    target_type=['ProductionProfile', 'FieldReserves'])

# Filter target node properties
graph.select('Field').traverse('HAS_LICENSEE',
    where={'title': 'Equinor Energy AS'})

# Filter edge properties
graph.select('Person').traverse('RATED',
    where_connection={'score': {'>': 4}})

# Temporal filtering
graph.select('Field').traverse('HAS_LICENSEE', at='2005')
graph.select('Field').traverse('HAS_LICENSEE',
    during=('2000', '2010'))
union(other: KnowledgeGraph) KnowledgeGraph

Combine selections from both graphs (set union).

Returns:

A new KnowledgeGraph with nodes from either selection.

unique_values(property: str, group_by_parent: bool | None = None, level_index: int | None = None, indices: list[int] | None = None, store_as: str | None = None, max_length: int | None = None, keep_selection: bool | None = None) Any

Get unique values of a property, optionally storing results.

Parameters:
  • property – Property name to extract unique values from.

  • group_by_parent – Group by parent node. Default True.

  • level_index – Target level in the selection hierarchy.

  • indices – Specific node indices.

  • store_as – If set, stores comma-separated unique values as this property on parents.

  • max_length – Max string length when storing.

  • keep_selection – Preserve selection after store. Default False.

Returns:

Dict of unique values per parent, or a KnowledgeGraph if store_as is set.

unlock_schema() KnowledgeGraph

Unlock the schema: allow any Cypher mutations without validation.

Returns:

Self for method chaining.

unspill() None

Move mmap-backed columnar data back to heap memory.

Useful after deleting nodes when you want data back in RAM for faster access. Internally rebuilds columnar stores from scratch with the memory limit temporarily suspended to prevent re-spilling.

No-op if the graph is not in columnar mode.

Example:

graph.unspill()
info = graph.graph_info()
assert not info['columnar_is_mapped']
update(properties: dict[str, Any], keep_selection: bool | None = None) dict[str, Any]

Batch-update properties on all selected nodes.

Parameters:
  • properties – Mapping of property names to new values.

  • keep_selection – Preserve the current selection in the returned graph. Default False.

Returns:

Dict with graph (updated KnowledgeGraph), nodes_updated (int), and report_index (int).

vacuum() dict[str, Any]

Compact the graph by removing tombstones left by node/edge deletions.

With StableDiGraph, deletions leave holes in the internal storage. Over time, this wastes memory and degrades iteration performance. vacuum() rebuilds the graph with contiguous indices, then rebuilds all indexes. If columnar storage is active, column stores are also rebuilt to eliminate orphaned rows from deleted nodes.

Important: This resets the current selection since node indices change. Call this between query chains, not in the middle of one.

Returns:

  • nodes_remapped: Number of nodes that were remapped

  • tombstones_removed: Number of tombstone slots reclaimed

  • columnar_rebuilt: Whether columnar stores were rebuilt

Return type:

dict with keys

Example:

info = graph.graph_info()
if info['fragmentation_ratio'] > 0.3:
    result = graph.vacuum()
    print(f"Reclaimed {result['tombstones_removed']} slots")
valid_at(date: str | None = None, date_from_field: str | None = None, date_to_field: str | None = None) KnowledgeGraph

Filter nodes valid at a specific date.

Keeps nodes where date_from <= date <= date_to.

If field names are not specified, auto-detects from set_temporal() config. If date is not specified, uses the date() context or today.

Parameters:
  • date – Date string (e.g. '2024-01-15'). Defaults to reference date or today.

  • date_from_field – Name of the start-date property. Auto-detected if temporal config exists.

  • date_to_field – Name of the end-date property. Auto-detected if temporal config exists.

Returns:

A new KnowledgeGraph with the filtered selection.

valid_during(start_date: str, end_date: str, date_from_field: str | None = None, date_to_field: str | None = None) KnowledgeGraph

Filter nodes whose validity period overlaps a date range.

If field names are not specified, auto-detects from set_temporal() config.

Parameters:
  • start_date – Start of the query range.

  • end_date – End of the query range.

  • date_from_field – Name of the start-date property. Auto-detected if temporal config exists.

  • date_to_field – Name of the end-date property. Auto-detected if temporal config exists.

Returns:

A new KnowledgeGraph with the filtered selection.

validate_schema(strict: bool | None = None) list[dict[str, Any]]

Validate the graph against the defined schema.

Parameters:

strict – Report undefined types in the graph. Default False.

Returns:

List of validation error dicts. Empty list means valid.

Vector similarity search within the current selection.

Searches for nodes most similar to the query vector among the currently selected nodes. Results are ordered by similarity (most similar first).

Parameters:
  • text_column – Source text column name (e.g. 'summary').

  • query_vector – The query embedding vector.

  • top_k – Number of results to return (default 10).

  • metric'cosine', 'dot_product', 'euclidean', or 'poincare'. If omitted, uses the metric stored via set_embeddings(metric=...), or falls back to 'cosine'.

  • to_df – If True, return a pandas DataFrame instead of list of dicts.

  • returning – Optional list of fields to project onto each hit. Omitted (default) → each hit carries id, title, type, score and all node properties. Given → each hit carries id + score plus only the named fields (a property name, or a structural field like title/type). Use it to trim the payload on wide nodes or ranking-only paths.

  • exact – Force an exact brute-force scan even when an HNSW index exists (see build_vector_index). Default False → a whole-corpus query on a large indexed store uses the approximate index; set True for guaranteed-exact results. Heavily-filtered selections and the 'poincare' metric are always exact regardless.

Returns:

List of dicts (or a DataFrame if to_df=True). By default a hit has id, title, type, score, and all node properties — score always present, properties read live so a hit is identical before/after save() + reload (no follow-up MATCH ... WHERE id IN [...] join needed). With returning=[...] a hit has id + score + the requested fields only.

Example:

# full hit (default)
results = (graph
    .select('Article')
    .where({'category': 'politics'})
    .vector_search('summary', query_vec, top_k=10))

# ranking-only / slim payload
ranked = graph.select('Article').vector_search(
    'summary', query_vec, top_k=50, returning=['title'])
where(conditions: dict[str, Any], sort: str | list[tuple[str, bool]] | None = None, limit: int | None = None) KnowledgeGraph

Filter the current selection by property conditions.

Conditions support exact match, comparison operators ('>', '<', '>=', '<='), 'in', 'is_null', 'is_not_null', 'contains', 'starts_with', 'ends_with', 'regex' (or '=~'), and negated variants: 'not_contains', 'not_starts_with', 'not_ends_with', 'not_in', 'not_regex'.

Example:

graph.select('Person').where({
    'age': {'>': 25},
    'city': 'Oslo',
    'name': {'regex': '^A.*'},
    'status': {'not_in': ['inactive', 'banned']},
})
Returns:

A new KnowledgeGraph with the filtered selection.

where_any(conditions: list[dict[str, Any]], sort: str | list[tuple[str, bool]] | None = None, limit: int | None = None) KnowledgeGraph

Filter the current selection with OR logic across multiple condition sets.

Each dict in conditions is a set of AND conditions (same as where()). A node is kept if it matches any of the condition sets.

Parameters:
  • conditions – List of condition dicts. Must contain at least one.

  • sort – Optional sort spec.

  • limit – Limit the number of selected nodes.

Returns:

A new KnowledgeGraph with the filtered selection.

Example:

graph.select('Person').where_any([
    {'city': 'Oslo'},
    {'city': 'Bergen'},
])
Raises:

ValueError – If conditions is empty.

where_connected(connection_type: str, direction: str | None = None) KnowledgeGraph

Filter nodes that have at least one connection of the given type.

Keeps only nodes from the current selection that participate in edges of the specified type and direction.

Parameters:
  • connection_type – Edge type to check (e.g. 'KNOWS').

  • direction'outgoing', 'incoming', or 'any' (default).

Returns:

A new KnowledgeGraph with only connected nodes.

Raises:

ValueError – If direction is not one of the valid values.

where_orphans(include_orphans: bool | None = None, sort: str | list[tuple[str, bool]] | None = None, limit: int | None = None) KnowledgeGraph

Filter nodes based on whether they have connections.

Parameters:
  • include_orphans – If True, keep only orphan (disconnected) nodes. If False, keep only connected nodes. Default True.

  • sort – Optional sort spec.

  • limit – Limit the number of selected nodes.

Returns:

A new KnowledgeGraph with the filtered selection.

within_bounds(min_lat: float, max_lat: float, min_lon: float, max_lon: float, lat_field: str | None = None, lon_field: str | None = None) KnowledgeGraph

Filter nodes within a geographic bounding box.

Parameters:
  • min_lat – South bound latitude.

  • max_lat – North bound latitude.

  • min_lon – West bound longitude.

  • max_lon – East bound longitude.

  • lat_field – Latitude property name. Default 'latitude'.

  • lon_field – Longitude property name. Default 'longitude'.

Returns:

A new KnowledgeGraph with only nodes in the bounding box.

wkt_centroid(wkt_string: str | Any, as_shapely: bool = False) dict[str, float] | Any

Calculate the centroid of a WKT geometry string.

Parameters:
  • wkt_string – WKT geometry string or shapely geometry object.

  • as_shapely – If True, return a shapely.geometry.Point instead of a dict.

Returns:

Dict with latitude and longitude, or a shapely Point when as_shapely=True.

property is_columnar: bool

Whether node properties are stored in columnar format.

Returns True if enable_columnar() has been called (or the graph was loaded from a v3 .kgl file).

property last_mutation_stats: dict[str, int] | None

Mutation statistics from the last Cypher mutation query.

Returns None if no mutation has been executed yet. Keys: nodes_created, relationships_created, properties_set, nodes_deleted, relationships_deleted, properties_removed.

property node_types: list[str]

List of node type names present in the graph.

property schema_locked: bool

Whether the schema is currently locked.

property shape: tuple[int, int]

(node_count, edge_count) — pandas-style. O(1) via the storage backend; does not materialise per-type breakdowns. Use schema() or describe() for the full structure.

kglite.okf.build(path: str, *, dialect: str | None = ..., require_frontmatter: bool = ..., respect_skip: bool = ..., skip_dirs: list[str] | None = ..., with_body: bool = ..., embed: bool = ...) kglite.KnowledgeGraph

Build a KnowledgeGraph from an OKF bundle directory.

A bundle is a directory tree of markdown files with YAML frontmatter, cross-linked by markdown links. Each non-reserved .md file becomes one node (label from the frontmatter type, or Concept when absent; id = the bundle-relative path minus .md); frontmatter keys become node properties (tags and nested maps are JSON-encoded); markdown links become typed edges. Link types are inferred most-specific-first: an explicit link title ([x](/y.md "JOINS_WITH")) → the enclosing section header (# CitationsCITES) → LINKS_TO. Links to not-yet-written concepts become _provisional stub nodes (MATCH (n {_provisional: true})).

The graph is enriched by default with synthesized nodes that densify it: Tag nodes ((:Concept)-[:TAGGED]->(:Tag) from tags), Source nodes (external http(s) links → (:Concept)-[:CITES]->(:Source)), and Folder nodes (the directory tree, (:Folder)-[:CONTAINS]-> concepts / subfolders, with each directory’s index.md enriching its Folder).

Ingestion is partial: the markdown body is not stored unless with_body is set — each node keeps a file_path pointer instead.

Parameters:
  • path – Bundle root directory.

  • dialect"okf" (default) for strict markdown links, or "loose" / "obsidian" to also resolve [[wikilinks]] and tolerate concepts with no frontmatter type.

  • require_frontmatter – When True (default), only .md files with a YAML frontmatter block are ingested — the discriminator between structured knowledge (OKF concepts, Claude memories) and plain markdown (READMEs, notes). Point at a parent of many projects to sweep out only the structured files across all of them. Set False to ingest every .md (vault-style). Node labels fall back typemetadata.typeConcept and titles titlename → file stem, so Claude memories land as :feedback / :project / etc. with their name as title.

  • respect_skip – When True (default), honor a kg_skip: true frontmatter marker that opts a file out of the sweep. Set False to ingest skip-marked files anyway.

  • skip_dirs – Directories to prune from the walk (the directory and its whole subtree). gitignore-style: an entry without a / matches a directory by name at any depth ("node_modules"); an entry with a / is an anchored bundle-relative path ("vendor/repos"). Use it to exclude cloned / vendored trees you don’t own.

  • with_body – Store each concept’s markdown body as a body property (off by default — bodies are read on demand).

  • embed – Reserved for the opt-in embedder pass (body vectors for text_score); not yet wired.

Returns:

A KnowledgeGraph of the bundle.

Raises:

RuntimeError – If the bundle path does not exist or is not a directory.

kglite.okf.source(path: str) str

Read a concept’s markdown body on demand (frontmatter stripped).

Pairs with partial ingestion: nodes store a file_path (relative to the bundle root), not the body. Join it with the root and pass it here to fetch the prose once a query has narrowed to a single concept. A file with no frontmatter returns its whole content.

Parameters:

path – Path to the concept’s .md file.

Returns:

The markdown body, with any leading YAML frontmatter removed.

Raises:

RuntimeError – If the file cannot be read.