Layer 1: Model
Pure data structures with no dependencies. Pydantic models for serialization.
Dataset Models
ftm_lakehouse.model.DatasetModel
Bases: Dataset
Source code in ftm_lakehouse/model/dataset.py
compression = None
class-attribute
instance-attribute
Compress exported artifacts (statements.csv, entities.ftm.json, diffs...)
public_url_prefix = None
class-attribute
instance-attribute
Public url prefix for resources
shards = DEFAULT_SHARDS
class-attribute
instance-attribute
Number of entity-id hash shards for the parquet store. 0 (default)
means a single shard; huge datasets should configure 8 or more at
creation for bounded per-partition working sets (e.g.
ensure_dataset("big_leak", shards=8)). Fixed once the store is
written: setting it here only changes where readers look, so changing
it after the fact means a full rewrite –
ShardOperation.
storage = None
class-attribute
instance-attribute
Set storage for external lakehouse
File Model
ftm_lakehouse.model.file.File
Bases: Stats
File metadata model. Arbitrary data can be stored in extra, including
ftm properties that should be added to the generated Entity
Source code in ftm_lakehouse/model/file.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | |
blob_path
property
Relative path to blob in dataset archive
checksum
instance-attribute
SHA256 checksum (often referred to as content_hash)
dataset
instance-attribute
Dataset name
extra = {}
class-attribute
instance-attribute
Arbitrary extra data
meta_path
property
Relative path for this file's metadata json in dataset archive
origin = None
class-attribute
instance-attribute
Origin stage of this file
make_id(data)
staticmethod
The entity id is generated by a hash of the file path and the checksum. Uses just the checksum as id if that's the key
Source code in ftm_lakehouse/model/file.py
make_parents()
to_entity()
Make an entity for this File
Source code in ftm_lakehouse/model/file.py
Statement Schema
Two schemas, one column apart. JOURNAL_SCHEMA is the producer schema – what every write path packs, what the journal table (journal_table) physically stores, and what the api wire format carries. SHARDED_SCHEMA prepends the shard partition key and is what parquet holds; ParquetStore.append derives that column from entity_id, so no producer carries a shard key of its own and none can route a row against a shard count other than the store's.
LakehouseStatement is the statement the write path passes around – ftmq's LakeStatement plus deleted_at, the tombstone marker. It deliberately carries no shard: a statement is content plus provenance, and where it lands is the store's call. statements_to_arrow is the one packer both statement write paths use: ftmq's statements_to_table packs the statement columns columnwise, this adds deleted_at, drops canonical_id, and applies the shared rules (first_seen / last_seen default, tombstone last_seen bump) as vectorized fills.
ftm_lakehouse.model.statement.LakehouseStatement
Bases: LakeStatement
A statement carrying the two columns the lakehouse adds to the schema.
deleted_at is the tombstone marker – a storage fact about a statement
rather than statement content, and lakehouse-only (ftmq's lake store
deletes physically). role records who asserted the statement: an
identifier a submitting application supplies, alongside origin's
where. Both live here for the same reason fragment lives on
ftmq.store.lake.LakeStatement: so the write path can pass statements
around instead of (stmt, deleted_at, role) tuples.
role joins origin and fragment in dedupe_key, so two roles
asserting identical content stay two rows through
merge – full
provenance, rather than one row whose role is whoever wrote last. The
empty string collapses to None so "no role" has one representation.
There is deliberately no shard attribute – a statement is content plus
provenance, and which partition it lands in is
append's call.
Source code in ftm_lakehouse/model/statement.py
dedupe_key
property
Stable row identity: id, origin, fragment, role.
Extends ftmq.store.lake.LakeStatement.dedupe_key with the
lakehouse's fourth identity dimension, so the write buffers collapse
re-emissions exactly where merge does.
ftm_lakehouse.helpers.statements.dedupe_key keeps the same key shape
for the packed-row paths that never build a statement object.
from_db_row(row)
classmethod
Read a statement back from a SQL row, keeping role.
Source code in ftm_lakehouse/model/statement.py
from_dict(data)
classmethod
Read a statement back from a row dict, keeping role.
deleted_at is deliberately not read back: every consumer of this
(statement queries, the api NDJSON wire) reads the live view, where
a surfaced row is by definition not a tombstone.
Source code in ftm_lakehouse/model/statement.py
ftm_lakehouse.model.statement.statements_to_arrow(statements, now)
Pack a stream of statements into a JOURNAL_SCHEMA table.
ftmq's statements_to_table packs the statement
columns columnwise; this adds the two columns the lakehouse stores on top
(role and deleted_at), drops canonical_id (this store never
resolves entities), and applies the two rules both write paths share:
first_seen/last_seenfall back tonowwhen the statement carries none,- tombstones (
deleted_atset) bumplast_seento the later of the delete timestamp and the row they shadow, so they win theROW_NUMBER() OVER (... ORDER BY last_seen DESC, deleted_at DESC NULLS LAST)tiebreak inParquetStore.merge. Taking the delete timestamp alone would lose to a row dated in the future – input carrieslast_seen, so nothing bounds it by the wall clock – andmergewould drop the tombstone rather than the row, leaving the entity undeletable on every retry. On the tie this leaves, thedeleted_attiebreak decides, which is what it is there for. first_seenis clamped toclamp_first_seen– after the tombstone rule, so it settles against the finallast_seen.
All three rules are vectorized fills over the packed columns rather than
per-row branches, and every column swap below is zero-copy. The closing cast is what
makes the result align with JOURNAL_SCHEMA – including its NOT
NULL columns, so a statement missing one is rejected here rather than by a
reader later.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
statements
|
Iterable[LakehouseStatement]
|
Statements, typically a whole drained
|
required |
now
|
datetime
|
Default timestamp for missing |
required |
Returns:
| Type | Description |
|---|---|
Table
|
A table with exactly |
Source code in ftm_lakehouse/model/statement.py
ftm_lakehouse.model.statement.journal_table(metadata, name)
Physical journal table named name, mirroring JOURNAL_SCHEMA.
The journal buffers exactly the rows producers pack, so its DDL is
derived from the same pyarrow schema – a journal row needs no packing to
become a statement row, and a segment can be streamed straight into
ParquetStore.append
as Arrow, which appends the derived shard partition key.
No primary key, no unique constraint, no index: the journal is an
append-only heap – but the schema's own NOT NULL columns
(REQUIRED_COLUMNS) still hold, so a row that could not be read
back never lands. Re-emissions accumulate as extra rows and
ParquetStore.merge
collapses them, which is where dedup lives anyway – and without a key, row
identity (origin, id, fragment, role) survives the journal instead of
collapsing to (id, fragment).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metadata
|
MetaData
|
The |
required |
name
|
str
|
Table name – the live journal or one of its segments. |
required |
Returns:
| Type | Description |
|---|---|
Table
|
The SQLAlchemy |
Source code in ftm_lakehouse/model/statement.py
Job Models
ftm_lakehouse.model.JobModel
Bases: BaseModel
Status model for a (probably long running) job
Source code in ftm_lakehouse/model/job.py
ensure_run_id(value=None)
classmethod
ftm_lakehouse.model.DatasetJobModel
Bases: JobModel
Status model for a (probably long running) job bound to a dataset