Skip to content

Implement filters - #156

Draft
azmeuk wants to merge 11 commits into
mainfrom
17-filters
Draft

Implement filters#156
azmeuk wants to merge 11 commits into
mainfrom
17-filters

Conversation

@azmeuk

@azmeuk azmeuk commented Sep 1, 2026

Copy link
Copy Markdown
Member

Fixes #17

@azmeuk
azmeuk force-pushed the 17-filters branch 6 times, most recently from 7b365f8 to 990c63c Compare September 6, 2026 14:02
ScimFilter parses the filter grammar of RFC7644 §3.4.2.2, on a new lark
dependency, and evaluates it against Python objects. Parsing, resolution
against a model and evaluation are separate stages, so FilterVisitor can
transpile a tree into a backend query rather than evaluate it. Binding a filter
checks it as it is created, against one resource type or against a union of
them: §3.4.2.1 has an attribute only some of them declare evaluate to false on
the others, so only an attribute none declares is refused. The grammar and the
resolution sit above the package, attrPath being one ABNF rule that RFC7644
uses in both FILTER and PATH (§3.5.2).
Path validates against the PATH rule of RFC7644 §3.5.2, as corrected by errata
7122, instead of a permissive character pattern, so a.b.c is now rejected. get,
set and delete honour the selection, letting PatchOp.patch address individual
entries of a multi-valued attribute: a selection matching nothing raises
noTarget for replace, as §3.5.2.3 mandates, and is a no-op for remove and add,
which §3.5.2.2 and errata 8097 leave that way. The filter between the brackets
is checked against the model before the resource is read, so an undeclared
attribute is reported as invalidFilter whether or not the selected attribute
holds values.
Path carried its own resolution next to the one the filter engine brought in,
and the two disagreed: attr rendered the whole parsed node, so a value
selection left every model-aware property None. Path.resolve is now the single
resolution, and over the 153 paths iter_paths yields for User,
User[EnterpriseUser] and Group, only x509Certificates.value changes, now
reported unwrapped as bytes.
SearchRequest.filter is a ScimFilter instead of a str, so a malformed filter is
rejected at validation time; it is a str subclass, so anything reading one
still reads a string. Naming the resource types the endpoint serves, as in
SearchRequest[User] or SearchRequest[User | Group], is what also refuses an
attribute none of them declares, where an unparameterised request only has its
syntax checked.
Parameterising a request names the resource types an endpoint serves, which
makes an attribute none of them declares a client error rather than something
to carry to the endpoint, so sort_by answers invalidPath at validation time the
way filter answers invalidFilter. An unknown attributes entry stays ignored:
dropping a projection degrades gracefully where dropping an order answers an
arbitrary one the client cannot tell from what it asked for.
Adds a filters page covering the grammar, the operators, the deviations from
the published ABNF and a worked transpiler built on FilterVisitor, and a
tutorial section on filters and PATCH value selections. The framework guides
now filter the collections they serve: a filter applies to the SCIM
representation, so the store is mapped first, filtered, paginated last.
Replaces the in-memory storage layer of the integration guides with a database,
where the filter becomes a WHERE clause next to the pagination, so a page costs
a query over the matching rows rather than a walk over all of them.
Twenty-three filters run both as a query and through match(), an oracle that
caught SQLite's naive datetime, its case-folding LIKE, and a case test on str
that skipped emails.value.
The examples already gather users and groups at the root, so the filter of a
root query is checked against both models: an attribute only one of them
declares evaluates to false on the other, as RFC7644 §3.4.2.1 requires, and one
neither declares is refused outright.
The ORDER BY of the SQLAlchemy guide followed none of the three rules of
RFC7644 §3.4.2.3: it kept the BINARY collation of SQLite where a non case-exact
attribute ignores case, inverted the placement of the missing values in both
directions, and refused a mapped sub-attribute such as meta.lastModified by
looking it up under its own name. The primary key now closes the clause, so a
page stays reproducible without a sortBy, and twelve orderings run both as a
query and through sort_resources.
sort_resources read the primary flag off whatever get() returned, which holds
entries for sortBy=emails but projected sub-attributes for sortBy=emails.value,
so any path naming a sub-attribute raised an AttributeError. RFC7644 §3.4.2.3
picks the entry first, primary or else the first, and the sub-attribute is read
from it, which turns sortBy=emails into the case where that sub-attribute is the
value RFC7643 §2.4 reserves for it. A scalar entry is that value itself, and an
extension left unset holds none.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support filters in SearchQuery and PatchOp

1 participant