dbt with Python
dbt manages warehouse transformations as versioned models - mostly SQL, with Python models where adapters allow pandas/polars-style logic in the warehouse runtime.
Search across all documentation pages
dbt manages warehouse transformations as versioned models - mostly SQL, with Python models where adapters allow pandas/polars-style logic in the warehouse runtime.
Quick-reference recipe card - copy-paste ready.
# models/staging/stg_orders.sql
select
order_id,
cast(revenue as double) as revenue,
region,
ordered_at
from {{ source('raw', 'orders') }}
where order_id is not null# models/marts/revenue_by_region.py (dbt Python model)
def model(dbt, session):
df = dbt.ref("stg_orders").to_pandas()
return df.groupby("region", observed=True)["revenue"].sum().reset_index()When to reach for this:
dbt build on every PR-- models/staging/stg_orders.sql
with source as (
select * from {{ source('ecommerce', 'orders') }}
),
cleaned as (
select
order_id,
upper(trim(region)) as region,
cast(revenue as numeric(18, 2)) as revenue,
ordered_at::timestamp_tz as ordered_at
from source
where revenue >= 0
)
select * from cleaned# models/staging/schema.yml
models:
- name: stg_orders
columns:
- name: order_id
tests: [unique, not_null]
- name: revenue
tests:
- not_null
- dbt_expectations.expect_column_values_to_be_between:
min_value: 0-- models/marts/fct_revenue_daily.sql
select
date_trunc('day', ordered_at) as revenue_date,
region,
sum(revenue) as total_revenue,
count(*) as order_count
from {{ ref('stg_orders') }}
group by 1, 2What this demonstrates:
source and ref dependency graphview, table, incremental).ref() - dbt build runs tests after model builds.manifest.json, catalog.json for lineage tools.| Layer | Role |
|---|---|
| staging | Rename, cast, light clean |
| intermediate | Business joins |
| marts | Consumer-facing facts/dims |
# Keep Python models thin - heavy lifting still belongs in SQL when possible
def model(dbt, session):
import pandas as pd
orders = dbt.ref("stg_orders")
pdf = orders.to_pandas() if hasattr(orders, "to_pandas") else orders
# feature engineering ...
return pdfunique/not_null on primary keys minimum.incremental_strategy and unique keys.target profiles dev/prod via env vars.| Alternative | Use When | Don't Use When |
|---|---|---|
| Pure SQL stored procs | Warehouse-native ops team | Want git PR workflow and tests |
| pandas scripts | Laptop-scale transforms | Warehouse is source of truth |
| Spark/dbt-spark | Huge cluster transforms | SQL marts suffice |
| SQLMesh | Need virtual environments per branch | Team already on dbt Cloud |
dbt debug && dbt build --select stg_orders+is_incremental() guard in model.schema.yml description fields flow to dbt docs site.tests:
- relationships:
to: ref('dim_customers')
field: customer_iduv lockfiles.pii in YAML meta.models/ lives beside app - separate CI job for dbt build.Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.
Reviewed by Chris St. John·Last updated Jul 16, 2026