Define typed Python entities, generate transformations, run anywhere. A dbt alternative built on Pydantic + Ibis.
Project description
Fyrnheim
Activities-first data transformation framework.
Built on Pydantic + Ibis. Define typed sources, detect business events from state changes, resolve identities across systems, and project entity models -- all in Python.
Install
pip install fyrnheim[duckdb]
Quick Start
1. Create a project:
fyr init myproject && cd myproject
2. Define your pipeline in entities/customers.py:
from fyrnheim import (
StateSource, ActivityDefinition, RowAppeared, FieldChanged,
IdentityGraph, IdentitySource, EntityModel, StateField,
)
# Source -- a slowly-changing state table
crm = StateSource(name="crm_contacts", project="p", dataset="raw", table="contacts", id_field="id")
# Activities -- named business events from state changes
signup = ActivityDefinition(name="signup", source="crm_contacts", trigger=RowAppeared())
became_paying = ActivityDefinition(
name="became_paying", source="crm_contacts",
trigger=FieldChanged(field="plan", to_values=["pro", "enterprise"]),
)
# Identity -- resolve across sources
identity = IdentityGraph(
name="customer_identity", canonical_id="customer_id",
sources=[IdentitySource(source="crm_contacts", id_field="id", match_key_field="email")],
)
# Entity -- derived current state
customers = EntityModel(
name="customers", identity_graph="customer_identity",
state_fields=[
StateField(name="email", source="crm_contacts", field="email", strategy="latest"),
StateField(name="plan", source="crm_contacts", field="plan", strategy="latest"),
],
)
3. Run tests:
pytest tests/
Core Concepts
Sources
StateSource -- a slowly-changing table (CRM contacts, subscription records). The diff engine automatically detects row appearances, disappearances, and field changes between snapshots.
StateSource(name="crm_contacts", project="p", dataset="d", table="contacts", id_field="contact_id")
EventSource -- an append-only event stream (page views, transactions).
EventSource(
name="billing_events", project="p", dataset="d", table="transactions",
entity_id_field="customer_id", timestamp_field="created_at", event_type_field="event_type",
)
Activity Definitions
Named business events detected from raw data changes. Each activity ties to a source and a trigger:
| Trigger | Detects |
|---|---|
RowAppeared() |
New row in a state source |
RowDisappeared() |
Row removed from a state source |
FieldChanged(field, to_values) |
Field value changed (optionally to specific values) |
EventOccurred(event_types) |
Specific event types in an event source |
signup = ActivityDefinition(name="signup", source="crm_contacts", trigger=RowAppeared())
became_paying = ActivityDefinition(
name="became_paying", source="crm_contacts",
trigger=FieldChanged(field="plan", to_values=["pro", "enterprise"]),
)
Identity Graph
Cross-source identity resolution. Link records from different systems by a shared match key:
IdentityGraph(
name="customer_identity",
canonical_id="customer_id",
sources=[
IdentitySource(source="crm_contacts", id_field="contact_id", match_key_field="email_hash"),
IdentitySource(source="billing_events", id_field="customer_id", match_key_field="email_hash"),
],
)
Entity Model
Derived current-state projection from resolved identities. Each field picks a source, a column, and a merge strategy (latest, first):
EntityModel(
name="customers",
identity_graph="customer_identity",
state_fields=[
StateField(name="email", source="crm_contacts", field="email", strategy="latest"),
StateField(name="first_seen", source="crm_contacts", field="created_at", strategy="first"),
],
computed_fields=[ComputedColumn(name="is_paying", expression="plan != 'free'")],
)
Analytics Model
Time-grain metric aggregation over the activity stream:
StreamAnalyticsModel(
name="daily_metrics",
identity_graph="customer_identity",
date_grain="daily",
metrics=[
StreamMetric(name="new_signups", expression="count()", event_filter="signup", metric_type="count"),
StreamMetric(name="total_customers", expression="count()", metric_type="snapshot"),
],
)
CLI
fyr init [project_name] # Scaffold a new project
fyr --version # Show version
fyr --help # Show available commands
Why Fyrnheim?
| dbt | Fyrnheim | |
|---|---|---|
| Language | SQL + Jinja | Python |
| Type safety | Runtime errors | Pydantic validation at definition time |
| Local dev | Requires warehouse connection | DuckDB on local parquet files |
| Backend portability | Dialect-specific SQL | Ibis compiles to 15+ backends |
| Testing | Custom schema tests | pytest |
| Identity resolution | Manual SQL joins | Built-in identity graph |
Status
- Alpha -- API may change before 1.0
- DuckDB backend -- fully supported
- BigQuery backend -- supported
- ClickHouse output -- supported as output sink
- Postgres backend -- supported
- Python 3.11+ required
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fyrnheim-0.5.0.tar.gz.
File metadata
- Download URL: fyrnheim-0.5.0.tar.gz
- Upload date:
- Size: 58.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e7db93fa9da336326db9c1fd0fbcc6c5b5f02e0be1882dff35b613e625f1bcb
|
|
| MD5 |
7502b886d85127c076fd1d6d3234c457
|
|
| BLAKE2b-256 |
4a22993346a1d0d856ae4d8637ec216a292dd1fee7c04cee4c53f93f7810b90a
|
Provenance
The following attestation bundles were made for fyrnheim-0.5.0.tar.gz:
Publisher:
publish.yml on deepskydatahq/fyrnheim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fyrnheim-0.5.0.tar.gz -
Subject digest:
0e7db93fa9da336326db9c1fd0fbcc6c5b5f02e0be1882dff35b613e625f1bcb - Sigstore transparency entry: 1262096260
- Sigstore integration time:
-
Permalink:
deepskydatahq/fyrnheim@e2607efb49d0eac923539c1751e3bf271e9fb931 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/deepskydatahq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e2607efb49d0eac923539c1751e3bf271e9fb931 -
Trigger Event:
push
-
Statement type:
File details
Details for the file fyrnheim-0.5.0-py3-none-any.whl.
File metadata
- Download URL: fyrnheim-0.5.0-py3-none-any.whl
- Upload date:
- Size: 80.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83a34867065b2a76d52387f14dcaf257f829bc4985ec19b5726f32197c317f72
|
|
| MD5 |
ee55d802713dcbedf93923736957d0eb
|
|
| BLAKE2b-256 |
7c84243808912192be5958df98f8eb10577a3502ebfe1c08fe1ae735a6b1f91d
|
Provenance
The following attestation bundles were made for fyrnheim-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on deepskydatahq/fyrnheim
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fyrnheim-0.5.0-py3-none-any.whl -
Subject digest:
83a34867065b2a76d52387f14dcaf257f829bc4985ec19b5726f32197c317f72 - Sigstore transparency entry: 1262096345
- Sigstore integration time:
-
Permalink:
deepskydatahq/fyrnheim@e2607efb49d0eac923539c1751e3bf271e9fb931 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/deepskydatahq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e2607efb49d0eac923539c1751e3bf271e9fb931 -
Trigger Event:
push
-
Statement type: