roboto.query.filters#
Filter controls as a user built them, in a form that survives being saved.
A SavedFilters records what someone expressed in a filter UI — which field, which
operator, which values — rather than the query that expression compiles to. Saved Views hold
this, and rebuild an executable query from it on load.
Why this exists rather than a QuerySpecification. A query cannot be
stored faithfully today, because Comparator has no way to say “the last
7 days”, “between these two dates”, or “any of these three”. Those get flattened at translation
time — a relative window resolves to fixed instants, a range becomes two comparisons, a
multi-select becomes an OR group — and the flattening has no inverse. A View storing the
translated query would show the week it was saved, forever, presented as though it were live.
This model is temporary by design. FilterOnlyComparator lists exactly what
Comparator cannot yet express. As that gap closes (ENG-2957), members are deleted from it
one at a time; when it is empty, a saved filter is expressible as a plain QuerySpecification
and this module can be retired in favour of one.
On the per-variant comparator lists below. roboql.model.core declares its own
*_FIELD_COMPARATORS sets over the same Comparator enum, and four of them
(STRING_, NUMERIC_, ENUM_, TAG_) are identical to the sets here. The
overlap is not accidental, but the two are answering different questions: roboql states
what the query language supports for a field type, while these state what the filter
UI offers, which is deliberately narrower — a date filter presents <, > and
BETWEEN where DATETIME_FIELD_COMPARATORS carries all six ordering and equality
operators, and a boolean presents EQUALS alone. So they are expected to diverge
further, not converge.
They cannot currently be shared in any case: roboql imports from roboto, so
reusing its constants here would be a cycle. Merging them means moving those constants
into this package, which is tracked as ENG-2982 and is best done alongside ENG-2957 —
that work already has to visit every consumer of Comparator.
Module Contents#
- class roboto.query.filters.BooleanFilter(/, **data)#
Bases:
_FilterBaseTrue or false, or unset.
- Parameters:
data (Any)
- comparator: Literal[roboto.query.conditions.Comparator.Equals, roboto.query.conditions.Comparator.IsNull, roboto.query.conditions.Comparator.IsNotNull]#
- type: Literal['boolean'] = 'boolean'#
- values: list[bool] = None#
- class roboto.query.filters.DateFilter(/, **data)#
Bases:
_FilterBaseInstants and ranges. Values are ISO 8601 strings.
The only variant offering relative windows, which is where the fidelity problem this whole model exists for actually bites.
- Parameters:
data (Any)
- comparator: Literal[roboto.query.conditions.Comparator.LessThan, roboto.query.conditions.Comparator.GreaterThan, roboto.query.conditions.Comparator.IsNull, roboto.query.conditions.Comparator.IsNotNull, FilterOnlyComparator, FilterOnlyComparator, FilterOnlyComparator, FilterOnlyComparator, FilterOnlyComparator, FilterOnlyComparator]#
- type: Literal['date'] = 'date'#
- values: list[str] = None#
- class roboto.query.filters.EnumFilter(/, **data)#
Bases:
_FilterBaseEquality against a closed set of options.
- Parameters:
data (Any)
- comparator: Literal[roboto.query.conditions.Comparator.Equals, roboto.query.conditions.Comparator.NotEquals, roboto.query.conditions.Comparator.IsNull, roboto.query.conditions.Comparator.IsNotNull]#
- type: Literal['enum'] = 'enum'#
- values: list[str] = None#
- roboto.query.filters.FILTER_VARIANTS: Final[dict[str, type[pydantic.BaseModel]]]#
Every filter variant, keyed by its
typediscriminant.
- type roboto.query.filters.Filter = Annotated[Union[StringFilter, NumericFilter, MetricFilter, DateFilter, BooleanFilter, SetFilter, EnumFilter, IdentityFilter], pydantic.Field(discriminator='type')]#
One filter row.
typeselects the variant, and with it the operators on offer.
- class roboto.query.filters.FilterMatchMode#
Bases:
enum.StrEnumHow separate filters combine.
- And = 'AND'#
Every filter must match.
- Or = 'OR'#
At least one filter must match.
- class roboto.query.filters.FilterOnlyComparator#
Bases:
enum.StrEnumOperators a saved filter needs that
Comparatorcannot express.Every member is a gap in the query language, and this enum is the list of them. It is deliberately the complement of
Comparatorrather than a superset: a member here thatComparatorcan express is a stale entry, and a test asserts the two never overlap.The intended lifecycle is deletion. As
Comparatorgrows to cover these (ENG-2957), members are removed one at a time; the wire values are unchanged by that move, so filters saved beforehand keep parsing. When this enum is empty, the work is done.- Between = 'BETWEEN'#
An inclusive range. Translates to
GTEandLTE, which loses the fact that the author expressed one range rather than two independent bounds.
- Last30Days = 'LAST_30_DAYS'#
- Last7Days = 'LAST_7_DAYS'#
- Last90Days = 'LAST_90_DAYS'#
- ThisMonth = 'THIS_MONTH'#
Relative windows, resolved against “now” when the filter runs.
These are the members that matter. The others cost fidelity; these cost correctness — a resolved window is wrong the day after it is saved, and nothing about the stored value says so.
- Today = 'TODAY'#
- roboto.query.filters.IDENTITY_OPERATORS_BY_TYPE: Final[dict[roboto.principal.RobotoPrincipalType, IdentityOperators]]#
Which operators each principal type contributes, and the only statement of that pairing.
Exhaustive over
RobotoPrincipalTyperather than listing the types an audit column happens to hold today, so a new platform principal type is filterable as soon as it exists instead of being silently unaddressable. A test pins that.
- roboto.query.filters.IDENTITY_PRESET_COMPARATORS: Final[frozenset[IdentityComparator]]#
the comparator alone carries the predicate.
- Type:
The
IS_ANY_<TYPE>half. Valueless
- roboto.query.filters.IDENTITY_TYPES_BY_COMPARATOR: Final[dict[IdentityComparator, roboto.principal.RobotoPrincipalType]]#
The principal type each identity operator addresses. Derived from the pairing above.
- class roboto.query.filters.IdentityComparator#
Bases:
enum.StrEnumOperators over a principal-valued field, where the operator names a principal type.
An audit column such as
created_bystores a fully-qualified principal —user:<user_id>,device:<device_id>@<org_id>,invocation:<invocation_id>— so “created by a user” is a question about the type prefix and “created by this user” a question about the whole value. Putting the type in the operator is what lets a filter UI offer the matching directory to pick from, instead of asking for a hand-typed prefix.Each type contributes two operators (see
IDENTITY_OPERATORS_BY_TYPE): a value-bearingIS_<TYPE>and a valuelessIS_ANY_<TYPE>.Separate from
FilterOnlyComparatorbecause these are not gaps in the query language. Both halves are expressible today —IS_<TYPE>asEQUALSagainst each picked principal,IS_ANY_<TYPE>asLIKE '<type>:%'— so ENG-2957 will never delete them. They are a filter-UI affordance, and they outlive the gap enum.- IsAnyDevice = 'IS_ANY_DEVICE'#
- IsAnyIntegration = 'IS_ANY_INTEGRATION'#
- IsAnyInvocation = 'IS_ANY_INVOCATION'#
- IsAnyOrg = 'IS_ANY_ORG'#
- IsAnyUser = 'IS_ANY_USER'#
- IsDevice = 'IS_DEVICE'#
- IsIntegration = 'IS_INTEGRATION'#
- IsInvocation = 'IS_INVOCATION'#
- IsOrg = 'IS_ORG'#
- IsUser = 'IS_USER'#
- class roboto.query.filters.IdentityFilter(/, **data)#
Bases:
_FilterBaseA principal-valued field, filtered by principal type.
Audit columns (
created_by,modified_by) hold a fully-qualified principal string, so the operator names the type (IdentityComparator) and any values it takes are principals of that type — anIS_USERfilter carrying adevice:value is rejected, since it records an intent the picker cannot express and a query cannot satisfy.Values are labeled options rather than bare strings: a principal id is not a name a reader can place, so the directory’s display name is captured alongside it at pick time.
Has no presence axis. Every write path stamps an audit principal, so the column is never null and a null check would be an operator that always answers the same way.
- Parameters:
data (Any)
- comparator: Literal[IdentityComparator, IdentityComparator, IdentityComparator, IdentityComparator, IdentityComparator, IdentityComparator, IdentityComparator, IdentityComparator, IdentityComparator, IdentityComparator]#
- type: Literal['identity'] = 'identity'#
- values: list[LabeledOption] = None#
- class roboto.query.filters.IdentityOperators#
Bases:
NamedTupleThe operator pair one principal type contributes to an identity field’s menu.
- comparator: IdentityComparator#
The value-bearing
IS_<TYPE>.
- preset: IdentityComparator#
The valueless
IS_ANY_<TYPE>.
- class roboto.query.filters.LabeledOption(/, **data)#
Bases:
pydantic.BaseModelOne option as it was picked: the value a query is built from, plus what the picker showed.
Both halves are stored because the label cannot be recovered later. An opaque value —
user:usr_01J..., a tag id — renders as itself, and resolving it on load would mean a directory lookup per chip, against an org that whoever opens a shared View may not be able to read. Onlyvalueever reaches a query.- Parameters:
data (Any)
- label: str#
What the picker displayed when the author chose this option. Display only, never queried.
- model_config#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- value: str#
What the query is built from. A fully-qualified principal, for an identity filter.
- roboto.query.filters.METRIC_FIELD_PATTERN: Final[str] = '^metric\\..+'#
- roboto.query.filters.METRIC_FIELD_PREFIX: Final[str] = 'metric.'#
Prefix distinguishing a user-defined metric from an ordinary numeric property.
Metric filters store the prefixed form so that
fieldmeans the same thing here as it does in aCondition, and so the eventual migration to a query copies the field across rather than special-casing it.
- type roboto.query.filters.MetricField = Annotated[str, pydantic.StringConstraints(pattern=METRIC_FIELD_PATTERN)]#
A metric’s dot-delimited path, carrying its
metric.prefix.
- class roboto.query.filters.MetricFilter(/, **data)#
Bases:
_FilterBaseOrdering and equality over a user-defined metric.
Numeric in every respect except that
fieldis a metric path. Kept a distinct variant so a client restoring a View knows to reopen the metric picker rather than the property form, which it cannot infer from the field name alone.Carries the presence pair like any other scalar type. A session may simply have no such metric recorded, and the session query path answers that directly — it maps
IS_NULLto aNOT EXISTSover the metrics table.- Parameters:
data (Any)
- comparator: Literal[roboto.query.conditions.Comparator.Equals, roboto.query.conditions.Comparator.NotEquals, roboto.query.conditions.Comparator.GreaterThan, roboto.query.conditions.Comparator.LessThan, roboto.query.conditions.Comparator.GreaterThanOrEqual, roboto.query.conditions.Comparator.LessThanOrEqual, roboto.query.conditions.Comparator.IsNull, roboto.query.conditions.Comparator.IsNotNull]#
- field: MetricField#
The field being filtered, as the query layer addresses it.
- type: Literal['metric'] = 'metric'#
- unit: str | None = None#
The metric’s unit, copied from its definition when the filter was built.
Denormalized for display: the filter chip renders it beside the value (“path_deviation > 1.5 m”) without looking the definition up.
Nonewhen the definition declares no unit.Being a copy, it goes stale if the definition’s unit later changes — a saved View would render the old one. Tolerable while it is only a label, and an argument for
view_v2resolving it from the definition rather than storing it. See ENG-2957.
- values: list[float] = None#
- class roboto.query.filters.NumericFilter(/, **data)#
Bases:
_FilterBaseOrdering and equality over a numeric property.
- Parameters:
data (Any)
- comparator: Literal[roboto.query.conditions.Comparator.Equals, roboto.query.conditions.Comparator.NotEquals, roboto.query.conditions.Comparator.GreaterThan, roboto.query.conditions.Comparator.LessThan, roboto.query.conditions.Comparator.GreaterThanOrEqual, roboto.query.conditions.Comparator.LessThanOrEqual, roboto.query.conditions.Comparator.IsNull, roboto.query.conditions.Comparator.IsNotNull]#
- type: Literal['numeric'] = 'numeric'#
- values: list[float] = None#
- roboto.query.filters.PRESENCE_COMPARATORS: Final[frozenset[roboto.query.conditions.Comparator]]#
the comparator alone carries the question.
- Type:
Null checks. Valueless
- roboto.query.filters.PRESET_COMPARATORS: Final[frozenset[FilterOnlyComparator]]#
the comparator alone carries the range.
- Type:
Relative windows. Valueless
- class roboto.query.filters.SavedFilters(/, **data)#
Bases:
pydantic.BaseModelA complete set of filter controls, as saved.
- Parameters:
data (Any)
- filters: list[Filter] = None#
The rows, in the order the author added them.
- match_mode: FilterMatchMode#
Whether the rows are combined with AND or OR.
Deliberately a single flag rather than a nested boolean expression. A
Conditioncan express arbitrary nesting, but a filter UI cannot build one legibly, so this records the shape the UI actually offers.
- model_config#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class roboto.query.filters.SetFilter(/, **data)#
Bases:
_FilterBaseMembership in a collection-valued field, such as tags.
Has no presence axis: an empty collection is not the same as an absent one, and the UI offers no null check here.
- Parameters:
data (Any)
- comparator: Literal[roboto.query.conditions.Comparator.Contains, roboto.query.conditions.Comparator.NotContains]#
- type: Literal['set'] = 'set'#
- values: list[str] = None#
- class roboto.query.filters.StringFilter(/, **data)#
Bases:
_FilterBaseText matching.
- Parameters:
data (Any)
- comparator: Literal[roboto.query.conditions.Comparator.Equals, roboto.query.conditions.Comparator.NotEquals, roboto.query.conditions.Comparator.Contains, roboto.query.conditions.Comparator.NotContains, roboto.query.conditions.Comparator.Like, roboto.query.conditions.Comparator.NotLike, roboto.query.conditions.Comparator.IsNull, roboto.query.conditions.Comparator.IsNotNull]#
- type: Literal['string'] = 'string'#
- values: list[str] = None#
- roboto.query.filters.comparators_by_type()#
The operators each filter type offers, as wire values.
Derived from the models rather than restated, so it cannot fall out of step with what they actually accept. Two callers: anything building a filter that needs to know what is valid for a field type, and the drift check against the filter UI’s own copy of this vocabulary — there is no code generation between the two, so a test compares them.
- Return type:
dict[str, list[str]]