Stores¶
Entity resolution is iterative, long-running, and can easily explode in compute and memory. matchlab uses caching for stability and sharing. Stores are how you configure it.
matchlab.stores.default
¶
Functions and variables for the default matchlib store.
Functions:
-
default_store–Return the default store, creating a DuckDB store in the cache dir if unset.
-
set_default_store–Set the store used by
collect()when none is passed.Noneresets it.
Attributes:
matchlab.stores.base
¶
The storage contract for matchlab.
Storage persists the artifacts each collected DAG step produces, keyed by that step's content fingerprint, and reads them back. It does not resolve anything on demand. Resolvers materialise their complete, merge-forward output at collect time and hand the store a finished table.
Artifacts, by step kind (schemas in matchlab.core.schemas, which holds exactly the
shapes that cross this boundary):
- Source → warehouse extract (arbitrary schema) + leaf assignment
(key, leaf). - Transform → the reshaped record step it materialises (arbitrary schema).
- Model → edge list,
SCHEMA_MODEL_EDGES(left_id, right_id, score). - Resolver → complete flat output,
SCHEMA_RESOLVER_OUTPUT(root, leaf, key, src). This is the merge-forward guarantee. It computesmerge(upstream complete output, own clusters), not just the resolver's own clusters.
A Source and a Resolver produce a record step too, but neither stores one. A source
is fully materialised as its extract and derives its record step on read. A resolver
read is a re-derivable join over the source's extract and the resolver's own output.
Only a Transform stores its materialisation.
Plus evaluation storage (judgements + cluster expansion), publication (publish points
a label at a resolver's output) and close.
Nothing here deletes an artifact on the store's own initiative. A store keeps what
it is given until the owner disposes of it. prune is that disposal. It deletes only
what the caller has said it may, and never a published resolver output. See the guide's
"Reclaiming storage".
Classes:
-
StoreStats–What a store holds, and what it costs.
-
PruneResult–What a prune removed, and what that actually recovered.
-
Store–Fingerprint-keyed storage for collected DAG-step artifacts.
Functions:
-
format_bytes–Render a byte count the way someone reading a disk would.
Attributes:
StoreStats
¶
Bases: BaseModel
flowchart TD
matchlab.stores.base.StoreStats[StoreStats]
click matchlab.stores.base.StoreStats href "" "matchlab.stores.base.StoreStats"
What a store holds, and what it costs.
A store keeps everything collected into it, so it grows with every plan and every edit to one. Stores subclass this to report metrics that are relevant to them.
Attributes:
-
location(str) –Where the store is, as a reader would name it, such as a path, a URI, or
":memory:". Always present, because a store you cannot point at is one you cannot go and delete. -
bytes(int) –The store's size. For anything file-backed, this is a high-water mark. A store that briefly held 4 GB reports 4 GB after the rows are gone, because deleting inside a database file does not return space to the OS. That is the right number for a disk filling up, and the wrong one for "how much data do I have".
-
artifacts(dict[StepKind, int]) –How many artifacts of each step kind are stored.
-
labels(int) –How many published labels point into the store.
Methods:
-
describe–One clause saying what the store costs, for a collect to print.
artifacts
class-attribute
instance-attribute
¶
size
property
¶
size: str
This store's size, as a phrase.
A hook, because a bare figure can mislead. Bytes held in memory and bytes written to disk are not the same claim, and only the backend knows which one it just reported.
describe
¶
describe(since: StoreStats | None = None) -> str
One clause saying what the store costs, for a collect to print.
Parameters:
-
(since¶StoreStats | None, default:None) –An earlier reading, so the growth between them can be attributed to whatever happened in between. Without it the figure is the store's size and nothing more, which names no cause.
PruneResult
¶
Bases: BaseModel
flowchart TD
matchlab.stores.base.PruneResult[PruneResult]
click matchlab.stores.base.PruneResult href "" "matchlab.stores.base.PruneResult"
What a prune removed, and what that actually recovered.
Stores subclass this where they have more to say.
Attributes:
-
removed(int) –How many artifacts were deleted.
-
kept(int) –How many survived. This includes the ones named, the ones their lineage needed, and everything published.
-
reclaimed(int) –Bytes genuinely returned. Measured as the store's size before minus after, not totted up from what was deleted. The two are not the same number. Deleting inside a database file usually frees nothing at all until the file is rewritten, and a reclaim that reported the bytes it deleted would claim to have freed space while the disk sat unchanged.
Methods:
-
describe–One line saying what happened, for a caller to print.
Store
¶
Bases: ABC
flowchart TD
matchlab.stores.base.Store[Store]
click matchlab.stores.base.Store href "" "matchlab.stores.base.Store"
Fingerprint-keyed storage for collected DAG-step artifacts.
Implementations are single-user and local. DuckDBStore is the reference.
Methods:
-
has–Return whether an artifact for this fingerprint is already stored.
-
store_source–Store a collected source.
-
read_source_extract–Return the cached warehouse extract for a stored source.
-
read_source_leaves–Return the
(key, leaf)assignment for a stored source. -
read_identifiers–Return
(id, source, key, leaf)for one source's records. -
store_model–Store a model's edge list (
SCHEMA_MODEL_EDGES). -
read_model–Return a stored model's edge list.
-
store_transform–Store a transform's materialised record step (arbitrary schema).
-
read_transform–Return a stored transform's record step.
-
store_resolver–Store a resolver's complete flat output.
-
publish–Point
labelatfp, replacing whatever it pointed at before. -
find–Return the fingerprint a label points at, if any.
-
labels–Return every label in this store, sorted.
-
source_key_field–Return which column of a stored source's extract holds the key.
-
read_source_records–Return a source's stored rows for
keys, every column qualified by name. -
resolver_output_sources–Return source name to fingerprint for a stored resolver output.
-
read_resolver–Return a resolver's stored output
(root, leaf, key, source). -
store_judgement–Persist a user judgement, expanding its clusters to leaves for scoring.
-
read_eval_data–Return
(judgements, expansion)tables formatchlab.core.eval. -
sample–Sample up to
nclusters from a stored resolver output for evaluation. -
stats–Report the store's size and contents.
-
prune–Delete every artifact except the ones named, and reclaim what that frees.
-
close–Release any underlying resources. Override if needed.
has
abstractmethod
¶
has(fp: Fingerprint) -> bool
Return whether an artifact for this fingerprint is already stored.
The client uses this to skip re-running a step whose plan is unchanged.
store_source
abstractmethod
¶
Store a collected source.
Parameters:
-
(fp¶Fingerprint) –The source step's fingerprint.
-
(key_field¶str) –Which column of
extractholds the key. Stored so the extract can be read back and joined to a resolver's output without the plan. -
(extract¶DataFrame) –The warehouse extract (arbitrary schema) to cache.
-
(leaves¶DataFrame) –The leaf assignment, columns
(key: str, leaf: uint64).
read_source_extract
abstractmethod
¶
read_source_extract(fp: Fingerprint) -> DataFrame
Return the cached warehouse extract for a stored source.
read_source_leaves
abstractmethod
¶
read_source_leaves(fp: Fingerprint) -> DataFrame
Return the (key, leaf) assignment for a stored source.
read_identifiers
abstractmethod
¶
read_identifiers(source_fp: Fingerprint, source_name: str, resolver_fp: Fingerprint | None = None) -> DataFrame
Return (id, source, key, leaf) for one source's records.
id is resolver_fp's root cluster when reading through a resolver, otherwise
the source's own leaf. This is the upstream output a downstream resolver
needs in order to carry every reachable leaf forward, including records no model
matched.
A query rather than an artifact. Nothing here is computed. Both readings are
projections of tables this store already holds, source_leaves and
resolver_output. There is nothing to cache, and caching it under a record
step's fingerprint would be wrong anyway. The result depends only on the source
and resolver read, not on how a record step reshapes the data.
Parameters:
-
(source_fp¶Fingerprint) –Fingerprint of the stored source whose records are wanted.
-
(source_name¶str) –The source's name, which is returned in the
sourcecolumn and tags each row for the resolver output below. -
(resolver_fp¶Fingerprint | None, default:None) –Fingerprint of the resolver to read through, or
Noneto read the source's own leaves.
store_model
abstractmethod
¶
store_model(fp: Fingerprint, edges: DataFrame) -> None
Store a model's edge list (SCHEMA_MODEL_EDGES).
read_model
abstractmethod
¶
read_model(fp: Fingerprint) -> DataFrame
Return a stored model's edge list.
store_transform
abstractmethod
¶
store_transform(fp: Fingerprint, table: DataFrame) -> None
Store a transform's materialised record step (arbitrary schema).
Called on every collect, so a transform feeding several models is computed once and read back by each.
read_transform
abstractmethod
¶
read_transform(fp: Fingerprint) -> DataFrame
Return a stored transform's record step.
store_resolver
abstractmethod
¶
store_resolver(fp: Fingerprint, resolver_output: DataFrame, sources: Mapping[str, Fingerprint] | None = None) -> None
Store a resolver's complete flat output.
Parameters:
-
(fp¶Fingerprint) –The resolver step's fingerprint.
-
(resolver_output¶DataFrame) –SCHEMA_RESOLVER_OUTPUTcolumns(root, leaf, key, source), already merged forward over all upstream leaves. The store does not verify the merge. That is the client's contract. The store does validate the schema. -
(sources¶Mapping[str, Fingerprint] | None, default:None) –Source name to fingerprint, for every source this output covers. A resolver's output names its sources but one store can hold several generations of a name, so this records which were actually used.
publish
abstractmethod
¶
publish(label: str, fp: Fingerprint) -> None
Point label at fp, replacing whatever it pointed at before.
The store moves the pointer without arguing. The caller decides whether overwriting is allowed, since it knows what the user asked for.
find
abstractmethod
¶
find(label: str) -> Fingerprint | None
Return the fingerprint a label points at, if any.
source_key_field
abstractmethod
¶
source_key_field(fp: Fingerprint) -> str
Return which column of a stored source's extract holds the key.
read_source_records
¶
read_source_records(source_fp: Fingerprint, source_name: str, keys: Series) -> tuple[DataFrame, str]
Return a source's stored rows for keys, every column qualified by name.
A query rather than an artifact, like read_identifiers, and concrete rather
than abstract because it is nothing but source_key_field and
read_source_extract composed.
The extract cached when the source was collected is the data the matching saw. That is what you want in front of you when judging a resolver's output, and it means both reading an entity and reviewing one work with no warehouse connection.
Parameters:
-
(source_fp¶Fingerprint) –Fingerprint of the stored source whose rows are wanted.
-
(source_name¶str) –The source's name, which every column is prefixed with.
-
(keys¶Series) –The keys to return rows for.
Returns:
resolver_output_sources
abstractmethod
¶
resolver_output_sources(fp: Fingerprint) -> dict[str, Fingerprint]
Return source name to fingerprint for a stored resolver output.
read_resolver
abstractmethod
¶
read_resolver(fp: Fingerprint) -> DataFrame
Return a resolver's stored output (root, leaf, key, source).
store_judgement
abstractmethod
¶
Persist a user judgement, expanding its clusters to leaves for scoring.
read_eval_data
abstractmethod
¶
Return (judgements, expansion) tables for matchlab.core.eval.
judgements follows SCHEMA_JUDGEMENTS. expansion follows
SCHEMA_CLUSTER_EXPANSION. Both filter to tag when it is given.
sample
abstractmethod
¶
sample(resolver_fp: Fingerprint, n: int, seed: int | None = None) -> DataFrame
Sample up to n clusters from a stored resolver output for evaluation.
Returns SCHEMA_RESOLVER_OUTPUT rows (root, leaf, key, source) for the
sampled roots.
stats
abstractmethod
¶
stats() -> StoreStats
Report the store's size and contents.
Abstract rather than an optional hook like close, because there is no honest
default. A store that reported zero would be wrong in exactly the place a user
is being asked to trust a number, and a store that cannot say how much room it
is taking is not one to hand a growing cache to.
Called on every collect, so it must be cheap. It may also settle pending writes
to report a size that has stopped moving, since DuckDBStore checkpoints.
This is not guaranteed to be a pure read.
prune
abstractmethod
¶
prune(keep: Iterable[Fingerprint] = ()) -> PruneResult
Delete every artifact except the ones named, and reclaim what that frees.
You say what to keep. Nothing is inferred. This is deliberately not the
gc() that used to live here. That one worked out what was still alive from
which Python objects happened to be reachable. A fresh interpreter has nothing
reachable, so it considered the whole store garbage and duly emptied one
that had a published resolver output in it. An artifact's worth has nothing to
do with whether some process is holding the variable that produced it. The root
set therefore arrives as an argument, from a caller who has just named it.
The caller names those roots as fingerprints. Step.fingerprints() gives the
fingerprints relevant to a plan, so storage does not have to learn to walk a
graph in order to know what it may delete.
Published labels are kept whether or not they are named, along with the sources their resolver output needs in order to stay readable. Publishing is the strongest "keep this" the system has, and losing one because a caller forgot to list it would be the old mistake in new clothes.
Implementations must:
- never touch stored judgements, which are human work and cannot be recomputed from anything
- report
reclaimedas space genuinely returned, measured rather than assumed. Deleting is not always the same as reclaiming, and a store that says it freed something it did not is worse than one that frees nothing.
Parameters:
-
(keep¶Iterable[Fingerprint], default:()) –Fingerprints to preserve. Published labels are kept whether or not their fingerprints are listed.
Returns:
-
PruneResult–What was removed and what that recovered.
Raises:
-
ValueError–If nothing would survive the prune, because
keepwas empty and nothing is published. Deleting everything is the same as deleting the file, and should not be what an accidentally-empty list does.