Filters¶
Sensors, triggers and the hook select records with filters, for example "artifacts whose key starts
with raw/". Build filters in Python with F and the
enums in airflow.providers.lamindb.utils.filters, so that
your IDE suggests the fields and operators and mistakes show up when the DAG is parsed:
from airflow.providers.lamindb.utils.filters import ArtifactField, ArtifactKind, F
large_raw_datasets = (
F(ArtifactField.KEY).startswith("raw/")
& (F(ArtifactField.KIND) == ArtifactKind.DATASET)
& (F(ArtifactField.SIZE) > 1_000_000)
)
Pass the filter as filter to
LaminDBRecordSensor,
LaminDBArtifactSensor,
LaminDBRecordEventTrigger,
LaminDBArtifactEventTrigger
and to the hook's query_records. They convert it to the JSON form of the
LaminHub REST API. You can also pass that JSON form directly:
{
"and": [
{"key": {"startswith": "raw/"}},
{"kind": {"eq": "dataset"}},
{"size": {"gt": 1000000}},
]
}
Fields¶
F takes a member of a field enum, such as ArtifactField.KEY, or a field name, such as "key".
There is a field enum for each core registry: ArtifactField, CollectionField, RunField,
TransformField, RecordField, ULabelField, FeatureField, SchemaField,
ProjectField, ReferenceField, StorageField, BranchField, SpaceField and
UserField. The enums list the fields of LaminDB 2.11; use strings for other fields, for example of
bionty registries: F("ontology_id").
LaminDBRegistry lists the registries for the registry argument, for example
LaminDBRegistry.RUN ("core.run") or LaminDBRegistry.BIONTY_CELL_TYPE
("bionty.celltype").
- Relations
- Pass several fields to
Fto follow relations.F(ArtifactField.CREATED_BY, UserField.HANDLE)is the handle of the user who created an artifact (created_by.handle), andF(RunField.TRANSFORM, TransformField.KEY)the key of a run's transform.Fchecks that the fields fit together. A relation can't be compared by itself; compare a field of the related records or the foreign key, e.g.F(ArtifactField.RUN_ID) == 42. - Counting related records
F(TransformField.RUNS).count() >= 2selects transforms with at least two runs.- JSON fields
- Index JSON fields with keys:
F(RunField.PARAMS)["learning_rate"] > 0.01.
Operators¶
Compare fields with Python's comparison operators or with the methods of F. Values can be enum
members (ArtifactKind.DATASET, RunStatus.COMPLETED), dates and datetimes, which are converted
to their JSON form.
F method |
Python operator | REST operator |
|---|---|---|
eq(value) / ne(value) |
== / != |
eq / ne |
gt(value) / gte(value) |
> / >= |
gt / gte |
lt(value) / lte(value) |
< / <= |
lt / lte |
is_in(values) / not_in(values) |
in / notin |
|
is_null() / is_not_null() |
== None / != None |
isnull |
startswith(prefix) / endswith(suffix) |
startswith / endswith |
|
contains(value) |
contains |
|
count(), compared with a number |
e.g. F(...).count() >= 2 |
count_eq, count_gte, ... |
op(FilterOperator.EQ, value) |
any operator |
FilterOperator lists all operators. Condition(field, operator, value) builds a condition
without F, e.g. Condition(ArtifactField.KEY, FilterOperator.STARTSWITH, "raw/").
Combining filters¶
Combine filters with & (and) and | (or), or with And(...) and Or(...). Python evaluates
& and | before comparisons, so wrap comparisons in parentheses:
from airflow.providers.lamindb.utils.filters import And, ArtifactField, F, Or
csv_or_parquet = (F(ArtifactField.SUFFIX) == ".csv") | (F(ArtifactField.SUFFIX) == ".parquet")
same = Or(F(ArtifactField.SUFFIX) == ".csv", F(ArtifactField.SUFFIX) == ".parquet")
also_same = F(ArtifactField.SUFFIX).is_in([".csv", ".parquet"])
recent_csv = And(F(ArtifactField.SUFFIX) == ".csv", F(ArtifactField.CREATED_AT) >= "2026-01-01")
LaminHub filters can't be negated; use !=, not_in() or is_not_null() instead. Python's
and, or, not and chained comparisons such as 1 < F(ArtifactField.SIZE) < 10 raise a
TypeError that explains the alternative.
Templates and validation¶
filter is a template field of the sensors, so values can be Jinja templates:
F(ArtifactField.KEY) == "{{ params.key }}".
Filters are validated when they are passed to a sensor, trigger or the hook, so that for example an
unknown operator such as {"key": {"equals": "a"}} fails when the DAG is parsed and not only when
the query runs.