Match attribute names on their case only - #167
Merged
Merged
Conversation
Four places answered the question of the name a field is serialized under, each with its own fallback, and only one of them fell back on the camel-cased field name. BaseModel._scim_name now answers it for all of them, so the name the resolution reads cannot drift from the name the serialization writes.
RFC7643 §2.1 makes attribute names case-insensitive, and its nameChar rule makes $, - and _ part of a name, but the normalisation stripped them all, so userName, user_name and u.s.e.r.n.a.m.e reached one field and a field carrying an explicit alias reached none. Every model now carries a table listing, for each of its fields, the spellings a payload may use, the validation hook rewrites each key through it, and pydantic reads the attribute under the SCIM spelling, which an error and a published JSON schema then carry.
Two SCIM names may yield one Python name, as employee_id and employeeId both yield employee_id, and the comprehension building the fields let the last one win: a dump then reported one value twice while losing the other. Each of them now gets a field, the attribute already spelled as that name keeping it and the others being held under their SCIM name, which the order of declaration cannot change. A schema declaring two names that only differ by case is still refused, RFC7643 §2.1 making them one attribute.
The nameChar rule of RFC7643 §2.1 lists $ among the characters a name is made of, and errata 8924 adds the leading one of $ref, but the pattern the grammar interpolates only accepted it in first position, so an attribute a schema declares as user$name could be read from a payload yet named by no path nor filter. The keyword lookaheads widen accordingly, so an attribute named eq$x is still not read as an operator.
The constraint checks of a pathless operation resolved each attribute name themselves and gave up on the ones they could not, which an extension named by its schema URN always was, so an operation unassigning an extension declared required was answered success. The caller now resolves each name once, before refusing the ones the schema does not declare, and hands the checks the field that holds the constraints.
Pydantic advises against populate_by_name since 2.11 and will deprecate it in v3, the pair of validate_by_name and validate_by_alias telling apart what the single setting conflated. Both are set, which the documentation states to be strictly equivalent to what the models declared until now.
azmeuk
force-pushed
the
166-attr-aliases
branch
from
September 20, 2026 06:59
c8034ce to
711da2c
Compare
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.
Attribute names were normalised by stripping every non-alphanumeric character before folding the case, so:
userName,user_nameandu.s.e.r.n.a.m.eall reached one field,employee-id,employee_idandemployeeIdbuilt two fields for three attributes, dumping one value twice and losing the others.RFC7643 §2.1 makes attribute names case-insensitive and nothing else, its
nameCharrule making$,-and_part of a name, so only the case is folded now: every model carries a table naming each of its fields by the spellings a payload may use, the validation hook rewrites each key through it, and pydantic reads the attribute under the name SCIM spells it with, which is the name an error and a published JSON schema then carry.from_schemabuilds a field for every attribute a schema declares, the one already spelled as its Python name keeping it and the others being held under their SCIM name, and only refuses two names that differ by case alone, which RFC7643 makes one attribute. This is a breaking change: a key differing from an attribute name only by its punctuation is now an unknown attribute thatScimPolicy.unknowngoverns, in payloads as in paths, filters andsortBy.fixes #166