Skip to content

Scopes

Generated from the docstrings. Scopes are the graph-traversal entry points a Catalog exposes, such as cat.themes and cat.codelists.

DatasetScope

Scope for a single dataset — enables graph navigation.

Entry point: store.dataset("housing-stats").

Navigation::

ds = store.dataset("housing-stats")
ds.codelists  # CodelistsScope
ds.themes  # ThemesScope
ds.related("codelist")  # list[BaseDataset]
ds.upstream(depth=5)  # list[BaseDataset]
ds.downstream()  # list[BaseDataset]
ds.lineage_records()  # list[dict]

codelists property

codelists: _FilteredCodelistsScope

Navigate to codelists used by this dataset (lazy).

Resolves via the dataset's SDMX dataflow → DSD → codelist URNs.

themes property

themes: _FilteredThemesScope

Navigate to themes (concepts) assigned to this dataset (lazy).

related

related(edge: str) -> QueryBuilder[BaseDataset]

Datasets sharing an entity via edge type.

Returns a lazy QueryBuilder — chain .include(), .filter(), .limit(), .collect() etc. before materializing.

Gremlin equivalent: g.V(id).out(edge).in(edge).dedup()

Supported edges: "codelist".

Parameters:

Name Type Description Default
edge str

The kind of entity the datasets share.

required

Returns:

Type Description
QueryBuilder[BaseDataset]

A query over the datasets linked to this one through edge.

Raises:

Type Description
ValueError

edge is not a supported edge type.

Example

store.dataset("id").related("codelist").include("publisher").limit(10).collect()

upstream

upstream(
    *, relationship: str | None = None, depth: int = 10
) -> QueryBuilder[BaseDataset]

Transitive ancestors via lineage (recursive CTE).

Returns a lazy QueryBuilder — chain .include(), .filter(), .limit(), .collect() etc. before materializing.

Gremlin equivalent: g.V(id).repeat(in('derivedFrom')).times(depth)

Parameters:

Name Type Description Default
relationship str | None

Follow only lineage edges of this relationship; all of them when None.

None
depth int

How many lineage hops to follow.

10

Returns:

Type Description
QueryBuilder[BaseDataset]

A query over this dataset's ancestors, to depth hops.

Example

store.dataset("id").upstream(depth=3).include("publisher").collect()

downstream

downstream(
    *, relationship: str | None = None, depth: int = 10
) -> QueryBuilder[BaseDataset]

Transitive descendants via lineage (recursive CTE).

Returns a lazy QueryBuilder — chain .include(), .filter(), .limit(), .collect() etc. before materializing.

Gremlin equivalent: g.V(id).repeat(out('derivedFrom')).times(depth)

Parameters:

Name Type Description Default
relationship str | None

Follow only lineage edges of this relationship; all of them when None.

None
depth int

How many lineage hops to follow.

10

Returns:

Type Description
QueryBuilder[BaseDataset]

A query over this dataset's descendants, to depth hops.

Example

store.dataset("id").downstream().include("publisher").collect()

lineage_records

lineage_records(
    *,
    role: str = "either",
    relationship: str | None = None,
    confidence: str | None = None,
) -> list[dict[str, object]]

Return lineage rows involving this dataset as plain dicts.

DatasetsScope

Search across the catalog's datasets.

Entry point: cat.datasets. Replaces the older cat.search / cat.semantic_search / cat.hybrid_search triple — now a single find(query, mode=...) returning SearchResult[Hit[BaseDataset]].

find

find(
    query: str,
    *,
    mode: str = "bm25",
    lang: str = "en",
    limit: int = 50,
    offset: int = 0,
    operator: str = "or",
    fuzzy: int = 0,
    rrf_k: int = 60,
    oversample: int = 200,
) -> SearchResult[Hit[BaseDataset]]

Search datasets; returns Hit objects wrapping BaseDataset.

ThemesScope

Scope over all SKOS concept schemes in the catalog.

Entry point: store.themes.

collect

collect() -> list[SchemeScope]

Terminal: all concept scheme handles.

A plain list, not an ItemList: a SchemeScope is keyed by scheme URI and has no id, so it never satisfied what an item list is for. Use store.themes[uri] to address one.

SchemeScope

Scope over concepts within a single SKOS concept scheme.

Entry point: store.themes["https://example.com/themes"]. The scheme field is the scheme URI.

datasets property

datasets: QueryBuilder[BaseDataset]

Navigate to datasets tagged with any concept in this scheme (lazy).

Returns a QueryBuilder pre-filtered to datasets citing concepts from this scheme.

collect

collect() -> list[ConceptRef]

Terminal: materialize concepts in this scheme as ConceptRef objects.

Use .rows() for enriched tabular results after .enrich().

rows

rows() -> list[Row]

Terminal: return enriched results as Row objects (requires .enrich()).

resolve

resolve() -> ConceptScheme | None

Terminal: fully hydrate the scheme including all its concepts.

count

count() -> int

Terminal: number of concepts in this scheme.

labels

labels(lang: str = 'en') -> dict[str, str]

Return {notation: display_label} for concepts in this scheme.

enrich

enrich(*exprs: AliasedExpr, **kwargs: Expr) -> SchemeScope

Add computed fields. Returns new scope (lazy).

filter

filter(*exprs: Expr) -> SchemeScope

Keep items where expressions are truthy. Returns new scope (lazy).

sort_by

sort_by(expr: Expr, *, desc: bool = False) -> SchemeScope

Order items by expression. Returns new scope (lazy).

ConceptSchemesScope module-attribute

ConceptSchemesScope = ThemesScope

ConceptSchemeScope module-attribute

ConceptSchemeScope = SchemeScope

ConceptsScope

Search and direct lookup over indexed SDMX Concept rows.

Entry point: cat.concepts. Concepts carry definitional text in sdmx.concept.description — the single highest-value vector target in an SDMX catalog, since a query like "income inequality" finds "Gini coefficient" through its definition rather than its label.

find

find(
    query: str,
    *,
    scheme_urn: str | Sequence[str] | None = None,
    mode: str = "bm25",
    lang: str = "en",
    limit: int = 20,
    offset: int = 0,
    operator: str = "or",
    fuzzy: int = 0,
    rrf_k: int = 60,
    oversample: int = 200,
) -> SearchResult[Hit[ConceptHit]]

Search concept rows; returns Hit objects wrapping ConceptHit.

get

get(concept_urn: str) -> ConceptHit | None

Direct SQL lookup of a single concept by URN.

ConceptScope

Scope for a single concept within a scheme, identified by notation.

Entry point: store.themes["https://example.com/themes"]["13"].

datasets property

datasets: QueryBuilder[BaseDataset]

Navigate to datasets tagged with this concept (lazy).

collect

collect() -> Concept

Terminal: materialize this concept.

CodelistsScope

Scope over all codelists in the shared structural tables.

Entry point: store.codelists.

lang

lang(lang: str) -> CodelistsScope

Set language for label resolution. Returns new scope (lazy).

filter

filter(*, text_contains: str) -> CodelistsScope

Filter codelists by label text (case-insensitive). Returns new scope (lazy).

collect

collect() -> list[CodelistScope]

Terminal: all codelists as scope handles.

Use .rows() for enriched tabular results after .enrich().

rows

rows() -> list[Row]

Terminal: return enriched results as Row objects (requires .enrich()).

batch_labels

batch_labels(
    pairs: list[tuple[str, str]], lang: str | None = None
) -> dict[tuple[str, str], str]

Terminal: resolve labels for (codelist_urn, code_id) pairs in one query.

Returns a dict mapping (urn, code_id) → label. Falls back to the code ID itself when the requested language has no entry.

Parameters:

Name Type Description Default
pairs list[tuple[str, str]]

Sequence of (codelist_urn, code_id) tuples to resolve.

required
lang str | None

Language code; defaults to the scope's .lang() setting.

None

Returns:

Type Description
dict[tuple[str, str], str]

{(codelist_urn, code_id): label} for every pair that resolved.

dict[tuple[str, str], str]

A pair with no label in lang falls back to its code id.

enrich

enrich(
    *exprs: AliasedExpr, **kwargs: Expr
) -> CodelistsScope

Add computed fields. Returns new scope (lazy).

CodelistScope

Scope for a codelist identified by URN.

Entry point: store.codelist("urn:...:CL_GEO(1.0)") or store.codelists["urn:...:CL_GEO(1.0)"].

datasets property

datasets: QueryBuilder[BaseDataset]

Navigate to datasets using this codelist (lazy).

lang

lang(lang: str) -> CodelistScope

Set language for code label resolution. Returns new scope (lazy).

collect

collect() -> ItemList[Code]

Terminal: resolve codelist and return all codes.

Use .rows() for enriched tabular results after .enrich().

rows

rows() -> list[Row]

Terminal: return enriched results as Row objects (requires .enrich()).

count

count() -> int

Terminal: number of codes in this codelist.

label

label(lang: str = 'en') -> str | None

Terminal: human-readable label for this codelist in lang.

Resolves via the sdmxlib localized_text table.

batch_labels

batch_labels(
    code_ids: list[str], lang: str | None = None
) -> dict[str, str]

Terminal: resolve labels for code_ids in a single SQL query.

Returns a dict mapping code_id → label. Falls back to the code ID itself when the requested language has no entry.

Parameters:

Name Type Description Default
code_ids list[str]

Code IDs to resolve (e.g. ["ON", "QC", "BC"]).

required
lang str | None

Language code; defaults to the scope's .lang() setting.

None

Returns:

Type Description
dict[str, str]

{code_id: label} for every id that resolved. An id with no

dict[str, str]

label in lang falls back to the id itself.

enrich

enrich(
    *exprs: AliasedExpr, **kwargs: Expr
) -> CodelistScope

Add computed fields to per-code results. Returns new scope (lazy).

CodesScope

Search and direct lookup over indexed SDMX code rows.

Entry point: cat.codes. Wraps the BM25/vector/hybrid paths against entity_type="code" rows in the Lance index, plus a pure-SQL get(code_urn) for cases where the URN is already known.

find

find(
    query: str,
    *,
    codelist_urn: str | Sequence[str] | None = None,
    dataset_ids: Sequence[str] | None = None,
    mode: str = "bm25",
    lang: str = "en",
    limit: int = 20,
    offset: int = 0,
    operator: str = "or",
    fuzzy: int = 0,
    rrf_k: int = 60,
    oversample: int = 200,
) -> SearchResult[Hit[CodeHit]]

Search code rows; returns Hit objects wrapping CodeHit.

mode is one of "bm25" (default; Tantivy FTS), "vector" (cosine over embeddings — requires an embedding model, returns nothing for FTS-only codelists), or "hybrid" (RRF merge of the two).

get

get(code_urn: str) -> CodeHit | None

Direct SQL lookup of a single code by URN — no Lance index involved.

Resolves the code's labels in any one language present (default: prefer en, fall back to whichever language has the label). For multi-language access, fetch the underlying sdmx.code row directly.

CodeScope

Scope for a single code within a codelist.

Entry point: store.codelist("urn:...")["ON"].

datasets property

datasets: QueryBuilder[BaseDataset]

Navigate to datasets whose DSD uses the codelist containing this code (lazy).

collect

collect() -> Code

Terminal: resolve this code.

dataset_count

dataset_count() -> int

Terminal: number of datasets whose DSD uses the codelist containing this code.

DimensionsScope

Scope over dimensions of a dataset.

Entry point: store.dimensions(ds).

collect

collect() -> ItemList[DimensionInfo]

Terminal: all dimensions for this dataset's DSD.

count

count() -> int

Terminal: number of dimensions.

DimensionScope

Scope for a single dimension of a dataset.

Entry point: store.dimensions(ds)["GEO"].

codelist property

codelist: CodelistScope | None

Navigate to this dimension's codelist (lazy).

collect

collect() -> DimensionInfo

Terminal: materialize this dimension.

DimensionInfo

Plain data object describing a dimension of a dataset.

It has no lazy properties; navigate to the codelist through DimensionScope.codelist.

Mutable rather than frozen so it satisfies sdmxlib's SupportsId, the bound on ItemList[T] since 0.61 — a protocol declaring a writable id, which a frozen attrs class cannot provide. This is also the project convention (CLAUDE.md: domain objects use @define and copy with attrs.evolve()); the frozen spelling was the outlier.

ReferenceMetadataScope

Search over indexed SDMX reference-metadata sections.

Entry point: cat.reference_metadata. Where EditionView.codes searches short controlled-vocabulary labels, this searches the prose of methodology documents — reference guides, quality reports — indexed one chunk per section. Hits carry set_urn / attribute_path so a caller can pull the full typed section via EditionView.metadata_report or resolve the artefacts the document describes.

target_urn scopes the search to metadata attached to a given artefact — the semantic-search analogue of EditionView.metadata_for.

find

find(
    query: str,
    *,
    target_urn: str | None = None,
    mode: str = "bm25",
    lang: str = "en",
    limit: int = 20,
    offset: int = 0,
    operator: str = "or",
    fuzzy: int = 0,
    rrf_k: int = 60,
    oversample: int = 200,
) -> SearchResult[Hit[ReferenceMetadataHit]]

Search reference-metadata section chunks.

target_urn restricts results to sections whose MetadataSet targets that artefact URN — resolved via EditionView.metadata_for, so a semantic hit and its structured resolution stay consistent. Other arguments mirror the code/concept scopes (mode, lang, limit, offset, operator, fuzzy, rrf_k, oversample).

Expressions

expr

Sub-traversal expression language for scope-based graph queries.

Expressions describe computations over related graph data without executing any SQL. They are context-free objects — like pl.col() in Polars — and can be reused across .enrich(), .filter(), and .sort_by() on any collection scope.

Usage:

import statcatlib as sc

n_datasets = sc.each("datasets").count()
has_data = sc.each("datasets").exists()

store.themes["subjects"].enrich(n=n_datasets).filter(has_data).collect()

Expr

Base class for all expression nodes.

alias
alias(name: str) -> AliasedExpr

Name this expression result (like Polars .alias()).

count
count() -> CountExpr

Count items at the far end of the traversal.

exists
exists() -> ExistsExpr

Check if any items exist at the far end.

sum
sum(field: str) -> SumExpr

Sum a numeric field at the far end.

ids
ids() -> IdsExpr

Collect identifiers at the far end.

distinct
distinct(field: str) -> DistinctExpr

Select distinct values of a field at the far end.

filter
filter(**kwargs: object) -> FilteredExpr

Filter far-side nodes by attribute values.

count_distinct
count_distinct() -> CountDistinctExpr

Count distinct items at the far end.

TraversalExpr

Bases: Expr

A traversal along one or more edges from the current scope position.

Created by sc.each("edge1", "edge2", ...). Strings are always edge labels (table names or relationship names in the graph).

AliasedExpr

An expression with a named output column.

Created by expr.alias("name") or via **kwargs in .enrich().

CountExpr

Bases: Expr

COUNT(*) at the far end of a traversal.

ExistsExpr

Bases: Expr

EXISTS(...) — boolean, true if any far-side items exist.

SumExpr

Bases: Expr

SUM(field) at the far end of a traversal.

IdsExpr

Bases: Expr

LIST(id) — collect identifiers at the far end.

DistinctExpr

Bases: Expr

SELECT DISTINCT field — narrows to unique values.

CountDistinctExpr

Bases: Expr

COUNT(DISTINCT ...) at the far end.

FilteredExpr

Bases: Expr

A traversal with far-side filtering applied.

Created by sc.each("datasets").filter(status="current").

each

each(*edges: str) -> TraversalExpr

Create a sub-traversal expression starting from the current scope.

Each argument is an edge label (relationship name). Multiple arguments describe a multi-hop path::

sc.each("datasets")  # one hop
sc.each("datasets", "dimensions")  # two hops
sc.each("datasets", "dimensions", "codelist")  # three hops

The returned expression is context-free and can be used in .enrich(), .filter(), and .sort_by() on any collection scope.