Layer 4: Operation
Multi-step workflow operations that coordinate across repositories.
Base Classes
ftm_lakehouse.operation.base.DatasetJobOperation
Bases: DatasetHandle, Generic[DJ]
A (long-running) dataset operation, skipped while its target tag is newer than its dependencies; the job result is stored after a successful run.
Repositories come from the LRU-cached factories. Subclasses set target and
dependencies, or override get_target() / get_dependencies().
Source code in ftm_lakehouse/operation/base.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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 | |
get_dependencies()
get_target()
is_fresh()
Whether the target tag is newer than every dependency – nothing to do. Override where freshness is not a pair of timestamps.
Source code in ftm_lakehouse/operation/base.py
prepare()
Bring the dataset into the state handle reads from – no-op by default.
Runs before the freshness check and outside the target's window:
Tags.touch stamps the target with its entry time, so a dependency
moved inside the window would mark the result stale at once.
ExportOperation
drains the journal here.
Source code in ftm_lakehouse/operation/base.py
run(force=False, *args, **kwargs)
Run the operation unless fresh (force runs it anyway); in api mode
the job is delegated to the server's operations endpoint.
Source code in ftm_lakehouse/operation/base.py
CrawlOperation
Batch file ingestion from a source location.
ftm_lakehouse.operation.crawl.CrawlJob
Bases: DatasetJobModel
Job model for crawl operations.
Tracks the state and configuration of a crawl job.
Attributes:
| Name | Type | Description |
|---|---|---|
uri |
Uri
|
Source location URI to crawl |
prefix |
str | None
|
Include only keys with this prefix |
exclude_prefix |
str | None
|
Exclude keys with this prefix |
glob |
str | None
|
Include only keys matching this glob pattern |
exclude_glob |
str | None
|
Exclude keys matching this glob pattern |
make_entities |
bool
|
Add document entities to statement store |
existing |
HandleExistingMode | None
|
Skip files whose checksum is already archived, or overwrite |
Source code in ftm_lakehouse/operation/crawl.py
ftm_lakehouse.operation.CrawlOperation
Bases: DatasetJobOperation[CrawlJob]
Crawl workflow that archives files and creates entities.
Iterates through files in a source store, archives them to the file repository, and creates corresponding entities in the entities repository.
Example
Source code in ftm_lakehouse/operation/crawl.py
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 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 | |
get_uris()
Generate file uris to crawl.
Applies prefix, glob, and exclude filters to the source store.
Yields:
| Type | Description |
|---|---|
str
|
File uris to be crawled |
Source code in ftm_lakehouse/operation/crawl.py
handle_crawl(uri, run)
Handle a single crawl task.
Archives the file and creates a corresponding entity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
uri
|
str
|
File uri to crawl |
required |
run
|
JobRun[CrawlJob]
|
Current job run context |
required |
Returns:
| Type | Description |
|---|---|
datetime
|
Timestamp when the task was processed |
Source code in ftm_lakehouse/operation/crawl.py
ExportOperation
One pass over the statement store writes every export: exports/statements.csv, entities.ftm.json, exports/documents.csv and exports/documents.crawl.csv (crawled files only) with their diff series, exports/parents.csv (every folder a document can sit in, with its path) and exports/statistics.json. Then index.json registers them.
The (shard, bucket) pairs are swept in LAKEHOUSE_WORKERS processes. Each finished pair's encoded parts are appended to a temporary file beside its artifact, which is moved into place once every pair is in – a crashed export leaves the previous artifacts intact. A compressed artifact is therefore a multi-frame zstd or multi-member gzip file: GzipFile / ZstdFile read it whole, zlib.decompress(blob, 31) only its first member.
The entities diff follows the OpenSanctions delta format: ADD (every statement new), MOD (predates the window, changed in it), DEL (gone); ADD and MOD carry the whole entity. A documents diff is the csv with a leading op column.
ftm_lakehouse.operation.export.ExportKind
Bases: StrEnum
What each export artifact is called (Artifact.kind) – also the key an
export run reports its counts under.
Source code in ftm_lakehouse/repository/artifacts.py
ftm_lakehouse.operation.export.ExportJob
Bases: DatasetJobModel
Job model for the export.
Source code in ftm_lakehouse/operation/export.py
make_diff = True
class-attribute
instance-attribute
Also export the delta diff files of the diffable artifacts.
result = None
class-attribute
instance-attribute
What the run wrote, per artifact and per diff op.
ftm_lakehouse.operation.ExportOperation
Bases: DatasetJobOperation[ExportJob]
Export the dataset in one sweep over the entity stream – skipped while the exports are newer than the last content change.
Source code in ftm_lakehouse/operation/export.py
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 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 | |
dependencies = [tag.STATEMENTS_UPDATED]
class-attribute
instance-attribute
The content clock – a merge does not move it.
export(now)
Write every streamed artifact from one pass over the entities.
Each (shard, bucket) pair is swept into parts of every artifact in
LAKEHOUSE_WORKERS processes; an artifact is replaced only once every
pair is in, so a failed run leaves the previous one intact – and the
diff states and statistics.json only once every artifact is. Held
under the merge lock, so an optimize cannot vacuum the snapshot's
files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
now
|
datetime
|
When the run started – diff files are named after it. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, int]
|
Counts per artifact and per diff op. |
Source code in ftm_lakehouse/operation/export.py
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 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | |
export_index()
Write index.json, registering what the exports produced.
Source code in ftm_lakehouse/operation/export.py
handle(run, *args, **kwargs)
The sweep, then every artifact's freshness tag – none if it crashed –
then index.json, also for an empty store.
Source code in ftm_lakehouse/operation/export.py
ftm_lakehouse.repository.artifacts.DiffOp
Bases: StrEnum
What a diff entry says happened to an entity.
Ref. https://www.opensanctions.org/docs/bulk/delta/
Source code in ftm_lakehouse/repository/artifacts.py
ADD = 'ADD'
class-attribute
instance-attribute
The entity is new – every statement it has arrived in this window.
DEL = 'DEL'
class-attribute
instance-attribute
The entity is gone entirely.
MOD = 'MOD'
class-attribute
instance-attribute
The entity predates this window and changed in it – it gained statements, or lost some to a tombstone while staying alive.
OptimizeOperation
merge rewrites every dirty partition into one file – duplicates collapsed, first_seen folded to the min and last_seen to the max, tombstones older than LAKEHOUSE_GRACE_PERIOD_DAYS dropped – then vacuum deletes the replaced files. Reads reconcile un-merged rows, so this is an optimisation, not a precondition. merge holds only the merge lock (.LOCK-MERGE), so appends keep flowing; vacuum takes the exclusive .LOCK as well.
ftm_lakehouse.operation.maintenance.OptimizeJob
Bases: DatasetJobModel
Source code in ftm_lakehouse/operation/maintenance.py
retention_hours = 0
class-attribute
instance-attribute
Vacuum: retain obsolete files newer than this many hours.
ftm_lakehouse.operation.OptimizeOperation
Bases: DatasetJobOperation[OptimizeJob]
Optimize the parquet statement store: merge, then vacuum.
Merge rewrites each dirty (shard, bucket, origin) partition into one
file, keeping the latest row per statement id with min(first_seen) and
max(last_seen). Tombstones past the grace period are dropped; vacuum
deletes the replaced files.
Source code in ftm_lakehouse/operation/maintenance.py
ShardOperation
Change a dataset's shard count: drain the journal, rewrite each (bucket, origin) group onto the new shards (streamed, one Delta commit per group), then record the count in config.yml. Rows are moved, not deduped, so every partition comes out dirty – run optimize afterwards. Run it with writers stopped: .LOCK holds off parquet appends, not journal writes, and a flush between the rewrite and the config write would still use the old count.
ftm_lakehouse.operation.maintenance.ShardJob
Bases: DatasetJobModel
Source code in ftm_lakehouse/operation/maintenance.py
shards = Field(ge=0)
class-attribute
instance-attribute
Target entity-id hash shard count; 0 / 1 means a single shard.
ftm_lakehouse.operation.ShardOperation
Bases: DatasetJobOperation[ShardJob]
Change the dataset's shard count: rewrite the store, then the config.
The rewrite drains the journal and moves every (bucket, origin) group
into the new shard partitions (one atomic Delta commit per group); then the
count is written to config.yml and the factory caches are cleared. The
config goes last – it declares the layout to every other process – so a run
that dies in between is repaired by running it again (the rewrite is
idempotent). Every partition comes out dirty: run optimize afterwards.
Source code in ftm_lakehouse/operation/maintenance.py
is_fresh()
Whether the config already names the target count – not a tag pair,
so a hand-edited config reads as fresh; force overrides.
MigrateOperation
Apply the migrations registered in ftm_lakehouse.operation.migrations that a dataset has not seen, oldest first, stamping a migrations/<function name> tag for each – the function name is the migration id. Migrations are forward-only and idempotent: a run that dies resumes at the first untagged one, and force re-runs them all.
ftm_lakehouse.operation.maintenance.MigrateJob
Bases: DatasetJobModel
No parameters – a migrate run is always "everything outstanding".
Source code in ftm_lakehouse/operation/maintenance.py
ftm_lakehouse.operation.MigrateOperation
Bases: DatasetJobOperation[MigrateJob]
Apply the storage-layout migrations this dataset has not seen yet.
Runs ftm_lakehouse.operation.migrations.MIGRATIONS in order, stamping
tag.migration per
migration, so a run that dies halfway resumes at the first untagged one.
force re-runs all – migrations are idempotent.
Source code in ftm_lakehouse/operation/maintenance.py
outstanding
property
The registered migrations this dataset carries no tag for.
MakeOperation
Flush the journal, then export. The make CLI runs optimize in between by default.
ftm_lakehouse.operation.make.MakeJob
Bases: DatasetJobModel
ftm_lakehouse.operation.MakeOperation
Bases: DatasetJobOperation[MakeJob]
Flush the journal and run the export.
Never merges – reads reconcile un-merged rows; the make CLI runs
OptimizeOperation
first by default.
Source code in ftm_lakehouse/operation/make.py
dependencies = [tag.STATEMENTS_UPDATED]
class-attribute
instance-attribute
The content clock – prepare drains the journal
ahead of the freshness check, so buffered rows move it first.
handle(run, *args, **kwargs)
Run the export – one sweep over every artifact, then index.json.
Source code in ftm_lakehouse/operation/make.py
DownloadArchiveOperation
Export archive files to their original paths.
ftm_lakehouse.operation.download.DownloadArchiveJob
ftm_lakehouse.operation.DownloadArchiveOperation
Bases: DatasetJobOperation[DownloadArchiveJob]
Download the archive files to a target transforming into nice paths based on exported documents.csv