Repository navigation
Implement filters - #156
Merged
Merged
Conversation
azmeuk
force-pushed
the
17-filters
branch
9 times, most recently
from
September 8, 2026 12:37
7edb41c to
cc891d5
Compare
azmeuk
force-pushed
the
17-filters
branch
2 times, most recently
from
September 10, 2026 08:25
05adddc to
c6eabb1
Compare
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), and neither the classes a subscription answers nor the resolutions it caches outlive a resource type built at runtime.
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, and the three spellings the errata offers for one selection all answer that filter expressed against an entry.
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, reading the attribute off the normalised path so that the three spellings of one selection designate the same one, and over the 153 paths iter_paths yields for User, User[EnterpriseUser] and Group, only x509Certificates.value changes, now reported unwrapped as bytes.
Both are strings whose attribute names only mean something against a model, and both carried their own subscription, class cache and pydantic plumbing. They now derive from _BoundToModels, which keeps the generated class name and the error messages each of them used to build by hand, and turns whatever either gets wrong into a pydantic error, where only a path used to name the field it was read from and carry its scimType.
SearchRequest.filter is a ScimFilter instead of a str, so a malformed filter is rejected at validation time, as a pydantic error naming the field and carrying its scimType; 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.
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, and the root query binds every resource type it gathers.
Replaces the in-memory storage layer of the integration guides with a database, where the filter becomes a WHERE clause and the order an ORDER BY next to the pagination, so a page costs a query over the matching rows rather than a walk over all of them. The clause follows the three rules of RFC7644 §3.4.2.3, case folding included, and closes on the primary key so a page stays reproducible without a sortBy. Twenty-three filters and twelve orderings run both as a query and through match() and sort_resources, an oracle that caught SQLite's naive datetime, its case-folding LIKE, and a case test on str that skipped emails.value.
Reading, writing and removing through a path resolved it against the type of the resource by splitting the string again: a schema URN matched by prefix, the segments looked up one by one. Resolving against the bound model had moved to the attribute resolver, and so had value selections, so the same question was answered three ways. The walk over an instance now takes the AttrPath the path designates to the resolver, and lands on the objects a ResolvedAttribute names; the bare schema URN is recognised by one function the bound and the instance side share. The depth the string walk allowed is one the grammar refuses at construction, so nothing reachable is lost.
The constraint checks of a PATCH went through a private helper of Path that resolved the path and kept only the model and the head attribute, next to a local copy of the function answering the object an attribute lives on. Both are what Path.resolve and attribute_host already answer, so PatchOp calls them and Path carries nothing that only PatchOp reads.
Path carried two halves that shared nothing but the parsed form: what a path designates on a model, and how it reads and writes an instance. The second now lives in its own module, as functions taking the path they act for, and Path keeps its declarative surface next to the three methods that delegate to them.
Filters and paths share one grammar with two start rules, one syntax tree, one resolver and one evaluator, yet the modules answering for them were split between a filters package and five modules at the root of the distribution, with the public namespace re-exporting mostly what lived outside it. Every filter is built on attribute paths where a path may carry no filter, so the attribute path is the notion the whole toolkit rests on, and the package is named for it. scim2_models.filters is gone; from scim2_models.path import Path keeps working.
A one-line docstring opens each module of the package, so that a reader tells apart what answers for the grammar, the syntax tree, the resolution against a model, the evaluation, and the access to an instance.
Next to Attribute, the description of an attribute a schema publishes, the name read as a variant of it. The object is the binding of a syntactic attribute path to the Python class and field it designates on a model, which is what the evaluator and transpilers consume, and the name now says so.
…gnates Path carried six properties reading the attribute it designates, written before AttributeBinding existed and with its words turned around: path.model named the type holding the sub-attribute where the binding names the one holding the head. Five of them go, the binding carrying each under a target_ name, and get_annotation joins it. Path.model keeps the one answer no binding gives, the model a resource root or a bare schema URN designates, and answers None for a path that names an attribute.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #17