Skip to content

dbt Integration

Example

The dbt integration is experimental

Data Joinery pipelines can run inside dbt Python models. Put dbt's runtime object in the pipeline context, then use it in reader transformations to fetch upstream models and sources.

from dataclasses import dataclass
from typing import Annotated

from pyspark.sql import DataFrame, SparkSession

from data_joinery import Context, Pipeline, Project, transform
from data_joinery.backends.spark import SparkContext
from data_joinery.dbt import Dbt


@dataclass
class Order:
    customer_id: int
    amount: float


@dataclass
class Customer:
    customer_id: int
    name: str


@dataclass
class EnrichedOrder:
    customer_id: int
    amount: float
    name: str


@dataclass(frozen=True)
class PipelineContext(SparkContext):
    dbt: Dbt


@transform
def read_orders(
    dbt: Annotated[Dbt, Context()],
) -> Annotated[DataFrame, Project(Order)]:
    return dbt.ref("upstream_orders")


@transform
def read_customers(
    dbt: Annotated[Dbt, Context()],
) -> Annotated[DataFrame, Project(Customer)]:
    return dbt.source("raw", "customers")


@transform
def enrich_orders(
    orders: Annotated[DataFrame, Project(Order)],
    customers: Annotated[DataFrame, Project(Customer)],
) -> Annotated[DataFrame, Project(EnrichedOrder)]:
    return orders.join(customers, "customer_id").select("customer_id", "amount", "name")


def build_pipeline() -> Pipeline[PipelineContext]:
    pipeline = Pipeline(PipelineContext)

    orders = pipeline.add_step(read_orders)
    customers = pipeline.add_step(read_customers)
    enriched = pipeline.add_step(enrich_orders)

    orders >> enriched
    customers >> enriched

    return pipeline


def model(dbt: Dbt, session: SparkSession) -> DataFrame:
    dbt.config(packages=["data-joinery==0.1.0"])
    outputs = build_pipeline().run(PipelineContext(spark=session, dbt=dbt))
    return outputs["enrich_orders"]