Tags: python-scim/scim2-models
Tags
[0.10.2] - 2026-10-01 --------------------- Fixed ^^^^^ - :meth:`PatchOp.build_from <scim2_models.PatchOp.build_from>` leaves out the read-only sub-attributes of multi-valued attributes, such as a read-only ``members.display``. The patch it builds is no longer rejected.
[0.10.1] - 2026-09-30 --------------------- Removed ^^^^^^^ - The ``attributes`` and ``excluded_attributes`` parameters of :meth:`~scim2_models.BaseModel.model_dump` and :meth:`~scim2_models.BaseModel.model_dump_json`, deprecated in 0.8.0. Pass a :class:`~scim2_models.ResponseParameters` as ``response_parameters`` instead. :issue:`141` Fixed ^^^^^ - :class:`~scim2_models.ListResponse` reads the pagination capabilities of the :class:`~scim2_models.ScimProvider`, including a provider opened around the validation. The last page of a server that only supports cursor pagination no longer needs ``totalResults``. - The policy of a :class:`~scim2_models.ScimProvider` passed as ``scim_provider`` applies to the validation and the serialization. It wins over the policy of an open block. ``scim_policy`` still wins over both.
[0.10.0] - 2026-09-30 --------------------- Added ^^^^^ - Support for :rfc:`RFC9865 <9865>` - :meth:`Resource.replace <scim2_models.Resource.replace>` returns whether the replacement changes the resource, the order of multi-valued entries aside. A server can keep ``meta.version`` and ``meta.lastModified`` when a PUT changes nothing. - In the :attr:`~scim2_models.Context.BULK_REQUEST` context, an invalid operation of a :class:`~scim2_models.BulkRequest` no longer fails the whole request. It becomes its own failed result, with a ``status`` and an :class:`~scim2_models.Error`, as :rfc:`RFC7644 §3.7.3 <7644#section-3.7.3>` requires. Under a :class:`~scim2_models.ScimProvider`, each operation is read as the resource type its ``path`` targets. An endpoint that serves none of the types of the request fails the operation with ``invalidPath``. - :attr:`BulkOperation.endpoint <scim2_models.BulkOperation.endpoint>` and :attr:`BulkOperation.resource_id <scim2_models.BulkOperation.resource_id>` read the target of an operation from its ``path``. Changed ^^^^^^^ - :meth:`SCIMException.from_error <scim2_models.SCIMException.from_error>` reconstructs :class:`~scim2_models.InvalidCursorException`, :class:`~scim2_models.ExpiredCursorException` and :class:`~scim2_models.InvalidCountException` from an :class:`~scim2_models.Error` carrying the matching ``scimType``, as :rfc:`RFC9865 §2.1 <9865#section-2.1>` defines them. They used to fall back to the base :class:`~scim2_models.SCIMException`. - :attr:`AttributeBinding.urn <scim2_models.AttributeBinding.urn>` and the error messages that quote it spell the attribute as the schema declares it, such as ``userName``, whatever case the path used. - Validation errors locate the value with an attribute path, such as ``emails[1].value``, and no longer expose internal names of pydantic. Fixed ^^^^^ - A :class:`~scim2_models.ScimProvider` or :class:`~scim2_models.ScimPolicy` block that exits in a context where it did not enter, such as another thread or another asyncio task, raises an error. It used to remove the provider or the policy of another block. Security ^^^^^^^^ - The ``password`` of a :class:`~scim2_models.User` is case-exact, so ``password eq "SECRET"`` no longer matches ``secret``. RFC 7643 declares it case-insensitive, but compares passwords by salted hash (:rfc:`RFC7643 §4.1.1 <7643#section-4.1.1>`), and the rules of :rfc:`RFC7644 §5 <7644#section-5>` keep the case of passwords. - A filter or a PATCH path that nests more than 32 expressions, such as ``not(not(...))``, is refused with ``invalidFilter`` or ``invalidPath``. A deep one used to raise a :class:`RecursionError`. Parentheses and chains of ``and`` or ``or`` do not count. - Filters and paths from clients no longer grow memory without limit. Each unknown attribute name, and each spelling of a known one such as ``USERNAME``, used to stay in memory for the life of the process. Filters and paths longer than 1024 characters are no longer cached. - Write-only and never-returned attributes, such as ``password``, no longer appear in the ``repr`` of a model. A :class:`~scim2_models.PatchOperation` bound to a resource type, as in a ``PatchOp[User]``, leaves out a ``value`` that writes one of them. Validation error messages no longer include the input values, which :meth:`ValidationError.errors() <pydantic_core.ValidationError.errors>` still returns. - Under :attr:`RemoveValue.apply <scim2_models.ScimPolicy.RemoveValue.apply>`, a PATCH ``remove`` whose ``value`` has a key that is not an attribute name, such as ``"value pr or value"``, is refused with ``invalidValue``. - In a :class:`~scim2_models.BulkRequest`, the ``data`` of a ``PATCH`` operation must be a :class:`~scim2_models.PatchOp`, and the ``data`` of a ``POST`` or a ``PUT`` must be a resource. A full resource sent as the ``data`` of a ``PATCH`` used to be accepted with its read-only attributes, such as ``id`` and ``groups``. An invalid ``data`` only reports the errors of the type the method expects. - In the :attr:`~scim2_models.Context.BULK_REQUEST` context, the ``location``, ``status`` and ``response`` of an operation are ignored, and they are left out of a serialized request. A client can no longer make an operation look like it failed.
[0.9.0] - 2026-09-27 -------------------- Added ^^^^^ - :attr:`ScimPolicy.unmatched_path_filter <scim2_models.ScimPolicy.unmatched_path_filter>` can make a PATCH ``add`` or ``replace`` create the entry its path filter describes when no entry matches, as Microsoft Entra ID expects. By default, the operation still fails with ``noTarget``. Changed ^^^^^^^ - Python 3.11 is now the minimum supported version. - The enumerations, such as :class:`~scim2_models.Mutability`, are :class:`~enum.StrEnum`: :class:`str` and f-strings give their value, ``readOnly`` rather than ``Mutability.read_only``. - In a model built from a schema, an attribute named after a member of the model, such as ``copy``, is held as ``copy_``. Its SCIM name is unchanged. - A PATCH ``add`` whose path filter matches no entry, such as ``emails[type eq "work"].value`` on a user without a work email, now fails with ``noTarget`` instead of silently doing nothing. :meth:`Path.set <scim2_models.Path.set>` raises :class:`~scim2_models.NoTargetException` in that case when strict. - A PATCH ``add`` or ``replace`` on a filtered path, such as ``emails[type eq "work"]``, merges its value into the matching entries instead of replacing them (:rfc:`RFC7644 §3.5.2.3 <7644#section-3.5.2.3>`). The entries are updated in place, so the immutable sub-attributes of a group member cannot be changed this way. Fixed ^^^^^ - A :class:`~scim2_models.PatchOperation` with a null value keeps it when dumped. A ``replace`` that clears its target used to be sent without a value. - Setting a null value under an unset complex attribute or extension no longer creates an empty one, and no longer reports the resource as modified. - A :class:`~scim2_models.PatchOp` with no operation, or with an operation other than ``add``, ``remove`` and ``replace``, fails with ``invalidValue`` instead of a validation error without ``scimType``. - A PATCH ``add`` or ``replace`` on a complex attribute keeps the sub-attributes its value leaves out, instead of replacing the whole attribute (:rfc:`RFC7644 §3.5.2.3 <7644#section-3.5.2.3>`). - A PATCH ``add`` without a path adds to the multi-valued attributes in its value, like an ``add`` with a path, instead of replacing their values. - A key of a PATCH value can be an attribute path, such as ``name.givenName`` or ``urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:employeeNumber``, as Microsoft Entra ID and its SCIM Validator send. It used to be rejected as an undeclared attribute. - Undeclared attributes and sub-attributes in a PATCH value follow :attr:`ScimPolicy.unknown <scim2_models.ScimPolicy.unknown>`. - PATCH checks immutable attributes at every level and in every operation, including operations with no path, an empty path or a schema URN, and the removal of an extension. For example, the ``value`` of a group member can be added and removed, but not changed (:rfc:`RFC7643 §4.2 <7643#section-4.2>`). Setting a first value with ``replace``, or writing back the current value, is accepted. - PATCH checks read-only attributes at every level, such as the ``displayName`` of the enterprise ``manager``. A path to one is rejected. A value that contains one is rejected only if it changes it, so an attribute sent back as it was read is accepted. Okta, for example, sends back the ``id`` of a group it renames. - PATCH rejects a change to a required attribute only when it leaves the attribute unset (:rfc:`RFC7644 §3.5.2.2 <7644#section-3.5.2.2>`). Removing some values of a required multi-valued attribute, removing a sub-attribute of a required complex attribute, or adding an empty list is now accepted. Required sub-attributes and extensions are checked too. - A PATCH ``replace`` without a value fails with ``invalidValue`` instead of clearing its target. So does an operation whose path targets the resource or an extension with a value that is not an object, which used to be ignored. - A PATCH ``replace`` that sets several ``primary`` entries fails with ``invalidValue``, like ``add`` already did, instead of keeping one of them at random. - :meth:`PatchOp.patch <scim2_models.PatchOp.patch>` raises :class:`~scim2_models.InvalidValueException` when the attribute rejects a value, instead of a pydantic :class:`~pydantic.ValidationError` without ``scimType``. - :meth:`PatchOp.patch <scim2_models.PatchOp.patch>` no longer reports a resource as modified when an operation writes a complex or multi-valued value it already has. - A PATCH path to an undeclared attribute follows :attr:`ScimPolicy.unknown <scim2_models.ScimPolicy.unknown>`, like a value does. With ``ignore`` or ``keep``, the operation changes nothing instead of failing the whole patch with ``invalidPath``. An undeclared sub-attribute in a filter still fails with ``invalidFilter``. Security ^^^^^^^^ - A published schema can no longer break the models built from it, nor make :meth:`ScimProvider.from_discovery <scim2_models.ScimProvider.from_discovery>` raise another exception than :class:`~scim2_models.ScimProviderError`. Complex attributes are nested two levels deep at most, as :rfc:`RFC7643 §7 <7643#section-7>` allows for ``Schema``. - :func:`~scim2_models.get_model_by_payload` matches no model when ``schemas`` is not a list of strings.
[0.8.2] - 2026-09-25 -------------------- Added ^^^^^ - :meth:`SearchRequest.sort <scim2_models.SearchRequest.sort>` orders resources as :rfc:`RFC7644 §3.4.2.3 <7644#section-3.4.2.3>` describes, for a server keeping them in memory. Changed ^^^^^^^ - A filter on a write-only attribute, such as ``password``, only takes ``eq``, ``ne`` and ``pr``, :rfc:`RFC7643 §4.1.1 <7643#section-4.1.1>` comparing it for equality alone. Any other operator answers ``invalidFilter``. - :attr:`SearchRequest.sort_by <scim2_models.SearchRequest.sort_by>` refuses a complex attribute, such as ``name`` or ``addresses``, where :rfc:`RFC7644 §3.4.2.3 <7644#section-3.4.2.3>` asks for a sub-attribute, a binary attribute, and a write-only attribute, such as ``password``. A multi-valued attribute holding a ``value``, such as ``emails``, is still sorted on it. Fixed ^^^^^ - :attr:`Resource.id <scim2_models.Resource.id>` is case-exact, as :rfc:`RFC7643 §3.1 <7643#section-3.1>` declares it, so filters and orders on it respect the case. - The :class:`~scim2_models.Meta` sub-attributes are read-only, and ``resourceType`` and ``version`` are case-exact, as :rfc:`RFC7643 §3.1 <7643#section-3.1>` declares them. - :meth:`Resource.replace <scim2_models.Resource.replace>` keeps a write-only attribute, such as ``password``, that the replacement leaves out, and clears it on an explicit null only. A client never gets that value back, so a PUT built from a GET used to erase it. - A model built with :meth:`Resource.from_schema <scim2_models.Resource.from_schema>` or :meth:`Extension.from_schema <scim2_models.Extension.from_schema>` is named as its schema, and its docstring is the description of the schema, so ``to_schema`` publishes both again. A schema named ``petOwner`` used to build a ``PetOwner`` class, and to lose its description.
[0.8.1] - 2026-09-25 -------------------- Changed ^^^^^^^ - :meth:`Error.from_validation_error <scim2_models.Error.from_validation_error>` follows :rfc:`RFC7644 §3.12 <7644#section-3.12>`: an invalid value answers ``invalidValue``, where it used to be ``invalidSyntax`` or nothing, and an invalid payload structure answers ``invalidSyntax``. Fixed ^^^^^ - :meth:`~scim2_models.SCIMException.from_error` keeps the :class:`~scim2_models.Error` object it is built from, and :meth:`~scim2_models.SCIMException.to_error` gives it back. The status and the scimType a server sent used to be replaced by the ones of the exception class, which dropped everything it has no class for, such as a ``429`` status or a vendor specific scimType. - Validation error messages name attributes as SCIM spells them, such as ``userName`` instead of ``user_name``, and extensions by their schema URN.
[0.8.0] - 2026-09-20
--------------------
Added
^^^^^
- :class:`~scim2_models.ScimFilter` parses the ``filter`` query parameter and matches resources
against it with :meth:`~scim2_models.ScimFilter.match`. See :doc:`explanation/filters`.
:issue:`17`
- PATCH paths take a value selection, such as ``emails[type eq "work"].value``.
- :class:`~scim2_models.SearchRequest` and :class:`~scim2_models.ResponseParameters` take the
resource type an endpoint serves, as in ``SearchRequest[User]`` or ``SearchRequest[User |
Group]``. Their ``filter``, ``sortBy``, ``attributes`` and ``excludedAttributes`` resolve
against those models, so a misspelled attribute is caught at validation time.
- :meth:`Path.resolve <scim2_models.Path.resolve>` answers the
:class:`~scim2_models.AttributeBinding` a path designates.
- :meth:`ScimFilter.quote <scim2_models.ScimFilter.quote>` renders a value as a filter literal.
On Python 3.14, :class:`~scim2_models.ScimFilter` and :class:`~scim2_models.Path` take a
t-string and quote what is interpolated. See :doc:`how-to/build-filters`.
- :class:`~scim2_models.ScimProvider` describes a SCIM service: the models it serves, and the
objects its discovery endpoints answer. :meth:`~scim2_models.ScimProvider.from_discovery`
builds one from what a service publishes, and a service that cannot be described is refused
with :class:`~scim2_models.ScimProviderError`. See :doc:`how-to/describe-a-scim-service`.
:issue:`108`
- An extension may be declared required, as in ``User[Annotated[EnterpriseUser, Required.true]]``.
A creation or a replacement request that leaves it out is refused.
See :doc:`how-to/define-custom-models`. :issue:`105`
- :class:`~scim2_models.ScimPolicy` states how much a payload may depart from the specification
and still be read. Name a policy at the call, or open a ``with`` block on it or on a provider
carrying one. Every setting defaults to the strict reading, so nothing changes until one is
chosen. See :doc:`how-to/tolerate-a-nonconformant-peer`. :issue:`85` :issue:`108`
- :meth:`~scim2_models.BaseModel.model_dump` and
:meth:`~scim2_models.BaseModel.model_dump_json` take a ``response_parameters``, the
:class:`~scim2_models.ResponseParameters` a client sent. A :class:`~scim2_models.SearchRequest`
is one, so a server answering ``POST /.search`` passes the request it received. :issue:`141`
- :meth:`~scim2_models.BaseModel.model_validate`,
:meth:`~scim2_models.BaseModel.model_validate_json`,
:meth:`~scim2_models.BaseModel.model_dump` and
:meth:`~scim2_models.BaseModel.model_dump_json` take a ``scim_provider`` and a ``scim_spc``, so
that the rules the specification makes conditional on a declared capability are read. A ``with``
block opened on a provider lends both. See :doc:`how-to/describe-a-scim-service`.
- :meth:`~scim2_models.PatchOp.build_from` builds the patch turning one resource state into
another. Only the attributes the wanted state names take part in the comparison.
See :doc:`how-to/build-a-patch`. :issue:`104`
- Bulk messages are validated, in the new :attr:`~scim2_models.Context.BULK_REQUEST` and
:attr:`~scim2_models.Context.BULK_RESPONSE` contexts. Each operation's
:attr:`~scim2_models.BulkOperation.data` is checked as the single request it stands for.
See :ref:`helpers-bulk`. :pr:`149`
- lark is a new dependency.
Changed
^^^^^^^
- :meth:`Resource.from_schema <scim2_models.Resource.from_schema>` and
:meth:`Extension.from_schema <scim2_models.Extension.from_schema>` refuse a schema declaring
two attributes whose names only differ by case, and report both.
:rfc:`RFC7643 §2.1 <7643#section-2.1>` :issue:`166`
- Attribute names are matched case-insensitively, and nothing else: ``{"user-name": "x"}``, which
0.7 read as ``userName``, is now an unknown attribute that
:attr:`~scim2_models.ScimPolicy.unknown` governs. Paths, filters and ``sortBy`` resolve the same
way, and a model whose fields answer to one attribute name raises a :class:`TypeError` where it
is defined. :rfc:`RFC7643 §2.1 <7643#section-2.1>` :issue:`166`
- The bulk models take the resource type their operations carry, as in ``BulkRequest[User]`` or
``BulkRequest[User | Group]``, and raise a :class:`TypeError` when used bare. A payload the type
parameter does not cover is now refused, and a bulk response no longer dumps ``path``.
- :class:`~scim2_models.ListResponse` raises a :class:`TypeError` when used without the resource
type its entries carry, as :class:`~scim2_models.PatchOp` and the bulk models do. A bare
``ListResponse`` used to answer a pydantic error naming ``Resource``.
- A message type parameter must name resource types. ``ListResponse[str]`` used to build a class
that read anything as its entries.
- ``ListResponse[Resource]``, ``PatchOp[Resource]`` and their bulk counterparts stay writable where
a type is expected, and raise a :class:`TypeError` when they read or build a payload.
``PatchOp[Resource]`` used to be refused as a type, and ``ListResponse[Resource]`` used to read
payloads.
- :attr:`SearchRequest.filter <scim2_models.SearchRequest.filter>` is a
:class:`~scim2_models.ScimFilter` instead of a :class:`str`, so a malformed filter is rejected
at validation time.
- Paths are parsed with the :rfc:`RFC7644 §3.5.2 <7644#section-3.5.2>` grammar, so malformed ones
such as ``emails[`` or ``userName ==``, which used to be accepted, are now rejected.
- :attr:`Path.model <scim2_models.Path.model>` answers the model a path designates when it names
no attribute, and :data:`None` otherwise. :meth:`~scim2_models.Path.resolve` answers what it
used to.
- :attr:`SearchRequest.sort_by <scim2_models.SearchRequest.sort_by>` naming an attribute no
resource type declares answers ``invalidPath``, where it used to be carried to the endpoint.
- A PATCH ``remove`` carrying a ``value`` is refused with ``invalidValue``, at validation and
when applied. It used to remove the entries equal to that ``value``. Set
:attr:`~scim2_models.ScimPolicy.remove_value_as_filter` to keep reading it.
- A PATCH reaching an extension attribute takes the extended resource type, as in
``PatchOp[User[EnterpriseUser]]``. ``PatchOp[User]`` used to carry such an operation to the
endpoint, and now refuses a path its type parameter leaves out.
Removed
^^^^^^^
- ``Resource.get_by_schema`` and ``Resource.get_by_payload``, deprecated in 0.6.13. Use
:func:`~scim2_models.get_model_by_schema` and :func:`~scim2_models.get_model_by_payload`, whose
``resource_types`` parameter is named ``models``.
- The ``original`` parameter of :meth:`~scim2_models.BaseModel.model_validate`, deprecated in
0.6.7. Validate the payload, then call :meth:`~scim2_models.Resource.replace` on the result to
compare it against the stored resource: an immutable attribute that changed raises
:exc:`~scim2_models.MutabilityException` from ``replace`` instead of a
:exc:`~pydantic.ValidationError` from ``model_validate``.
- ``Path.field_name``, ``Path.field_type``, ``Path.is_multivalued``, ``Path.get_annotation`` and
``Path.urn``. Use :meth:`~scim2_models.Path.resolve`, whose
:class:`~scim2_models.AttributeBinding` carries them.
- ``Path.is_prefix_of`` and ``Path.has_prefix``. They compared the text of two paths, which
anything between brackets defeated.
Deprecated
^^^^^^^^^^
- The ``attributes`` and ``excluded_attributes`` parameters of
:meth:`~scim2_models.BaseModel.model_dump` and
:meth:`~scim2_models.BaseModel.model_dump_json`. Pass a
:class:`~scim2_models.ResponseParameters` as ``response_parameters`` instead; naming both
raises a :exc:`TypeError`. They will be removed in 0.9.0. :issue:`141`
Fixed
^^^^^
- A PATCH operation carrying no ``path`` that unassigns an extension declared
:attr:`Required.true <scim2_models.Required.true>` is refused. :issue:`166`
- A filter or a path accepts a ``$`` anywhere in an attribute name, as ``nameChar`` allows.
:issue:`166`
- A schema declaring several attributes that yield one Python name builds a field for each of
them: the attribute already spelled as that name keeps it, and the others are held under their
SCIM name. An attribute is read under the name SCIM gives it, as in ``resource["employeeId"]``.
:issue:`166`
- A pydantic error spells the attribute as SCIM does, ``userName`` and ``$ref``, and so does the
JSON schema a model publishes. :issue:`166`
- A field declaring its own ``alias`` is read under it, and an unknown attribute is refused under
the spelling the peer used. :issue:`166`
- An extension is read under its class name as well as under its URN, so
``User[EnterpriseUser](EnterpriseUser=extension)`` is accepted and a resource carrying an
extension survives a dump without aliases read back. :issue:`166`
- A bulk model indexed with something other than a resource type names itself in the error, where
``BulkRequest[str]`` used to tell the caller to write ``PatchOp[User]``.
- A subclass of a parameterized message, such as ``class Users(ListResponse[User])``, reads its
payloads with the type parameter it inherits. It used to raise an :exc:`IndexError`.
- A PATCH operation targeting an attribute of an extension answers for the constraints that
extension declares, where it used to look them up on the resource and find none. A refused
operation no longer leaves the extension instantiated on the resource.
- A PATCH operation carrying no ``path`` accepts a resource as its ``value``, and checks the
attributes it names against the model. They used to go through unexamined.
- A PATCH operation whose ``path`` names an attribute the resource schema does not declare is
refused with ``invalidPath``. :issue:`164`
- A refused PATCH ``add`` on a multi-valued attribute leaves the attribute as it was. The entry
used to be appended before being validated, and outlived the failure.
- A PATCH operation carrying no ``path`` marks the attributes it assigns as set, so
``exclude_unset`` dumps them.
- A PATCH ``replace`` of a :attr:`Required.true <scim2_models.Required.true>` attribute with a
null value or an empty array is refused, and an operation leaving such an attribute unassigned
answers ``invalidValue`` instead of ``mutability``.
- Attributes annotated :attr:`~scim2_models.Mutability.read_only` are left out of a payload
dumped in the :attr:`~scim2_models.Context.RESOURCE_PATCH_REQUEST` context, as they already are
in a creation or a replacement request.
- Paths resolve against the attributes a model declares instead of the values a resource carries,
so ``name.unknown`` is refused on a resource carrying no ``name``, and ``userName.foo`` answers
``invalidPath`` instead of raising an :exc:`AttributeError`.
- A path crossing a multi-valued attribute, such as ``emails.value``, reads, writes and removes
the sub-attribute of every entry, where it used to raise an :exc:`AttributeError` or do nothing.
- A path names a ``$ref`` sub-attribute under that spelling, as in ``members.$ref``, both when it
is parsed and in :meth:`~scim2_models.Path.iter_paths`.
- A schema URN carries its attribute behind a colon, so
``urn:ietf:params:scim:schemas:core:2.0:UserId`` no longer reads as the ``id`` of a ``User``,
and a URN that is neither the schema of the model nor that of one of its extensions designates
nothing.
- :meth:`Resource.replace <scim2_models.Resource.replace>` checks the immutable and read-only
sub-attributes of the entries a multi-valued attribute keeps.
- :meth:`Resource.to_schema <scim2_models.Resource.to_schema>` publishes the members of a string
enumeration as ``canonicalValues``, and describes the resource alone on
``User[EnterpriseUser]``, where it used to publish the extension URN as an attribute of the
``User`` schema.
- :attr:`GroupMember.display <scim2_models.GroupMember.display>` is ``readWrite``, as
:rfc:`RFC7643 §8.7.1 <7643#section-8.7.1>` declares it.
- Subscribing a :class:`~scim2_models.Path` to a model, and parameterizing a resource with an
extension, no longer keep those classes alive for as long as the process runs. A server
building a model per schema it discovers used to accumulate every one of them.
[0.7.0] - 2026-09-05
--------------------
Added
^^^^^
- :func:`~scim2_models.get_model_by_schema` and :func:`~scim2_models.get_model_by_payload` module functions.
They look models up by schema, accept any sequence of :class:`~scim2_models.ScimObject` subclasses,
and reflect the input types in the returned type.
- :class:`~scim2_models.ScimObject` and ``AnyScimObject`` are exposed in the public API, so that downstream projects can annotate values that are either resources or messages.
- :class:`~scim2_models.ExtensibleStringEnum` is exposed in the public API, so custom models
can define string attributes that suggest canonical values without restricting them.
- :meth:`~scim2_models.BaseModel.model_validate_json` takes a ``scim_ctx`` parameter, like the
other validation and serialization methods, so JSON payloads can be validated without being
decoded first. :issue:`150`
Changed
^^^^^^^
- ``model_dump`` and ``model_dump_json`` are defined on :class:`~scim2_models.BaseModel` instead of
:class:`~scim2_models.ScimObject`, so every model is dumped in a SCIM context by default.
Complex attributes dumped on their own use their SCIM attribute names:
``Name(family_name="Doe").model_dump()`` returns ``{"familyName": "Doe"}``
instead of ``{"family_name": "Doe"}``.
- :meth:`~scim2_models.BaseModel.model_dump` with ``scim_ctx=None`` returns the native pydantic
dump, ``None`` values included, as its documentation states. Pass ``exclude_none=True`` to get
the former output.
- :meth:`~scim2_models.Resource.replace` does not mark the fields it copies from the original
resource as set anymore, so ``model_fields_set`` only holds the attributes asserted by the
client, as defined by :rfc:`7644` §3.5.1.
- Attributes suggesting :rfc:`7643` canonical values accept values outside of their canonical
set, as :rfc:`7643` §2.3.1 only allows service providers to restrict them. This covers the
``type`` attribute of :class:`~scim2_models.Email`, :class:`~scim2_models.PhoneNumber`,
:class:`~scim2_models.Im`, :class:`~scim2_models.Photo`, :class:`~scim2_models.Address` and
:class:`~scim2_models.AuthenticationScheme`.
:issue:`34`
- ``str()`` on those attributes returns the SCIM value instead of the enum representation:
``str(Email.Type.work)`` returns ``"work"`` instead of ``"Type.work"``.
- Canonical values are matched case-insensitively, as :rfc:`7643` §2.2 makes those attributes
case-insensitive: ``Email(type="WORK").type`` is ``Email.Type.work``.
- The JSON schema of those attributes advertises the canonical values as ``examples`` instead of
a restrictive ``enum``.
- The ``schemas`` attribute of SCIM payloads is built from the model definition on
serialization, as it describes the serialized document rather than the object. It holds what
a peer asserted, and is empty when a payload omitted it, so the omission stays visible in
``model_fields_set``. Objects built by the caller are still filled, as the model they are
built from asserts their type.
- Resources omitting their ``schemas`` attribute are read instead of being rejected, which
covers the partial responses of :rfc:`7644` §3.4.3. Their type comes from the
:class:`~scim2_models.ListResponse` parameter, so a response holding several resource types
still cannot decide the type of an unlabelled resource. :issue:`20`
- A ``schemas`` attribute that does not contain the model base schema is rejected whatever the
validation context, as an object cannot contradict the model it is an instance of. It used to
be accepted without a SCIM context.
- The ``schemas`` attribute is not subject to attribute filtering anymore, as :rfc:`7643` §3
requires it in every representation. It lost its :attr:`~scim2_models.Returned.always`
annotation, which :rfc:`7643` does not define for it, and which the filtering exemption
replaces.
Fixed
^^^^^
- ``reference`` and ``binary`` attributes are case-exact, unless a schema explicitly states otherwise. :rfc:`7643` §2.3.6 and §2.3.7, `erratum 6001 <https://www.rfc-editor.org/errata/eid6001>`_
- :class:`~scim2_models.ResourceType` ``endpoint`` is case-exact. :rfc:`7643` `erratum 8475 <https://www.rfc-editor.org/errata/eid8475>`_
- :class:`~scim2_models.GroupMember` and :class:`~scim2_models.GroupMembership` ``value`` are case-exact, as they hold resource ``id`` values. :rfc:`7643` §3.1, in the spirit of `erratum 8472 <https://www.rfc-editor.org/errata/eid8472>`_
- :meth:`~scim2_models.Resource.from_schema` no longer crashes on ``reference`` attributes missing the optional ``referenceTypes``, and reads them as :class:`~scim2_models.URI` references.
- Looking a model up by schema no longer crashes when the model list mixes resources with messages such as :class:`~scim2_models.ListResponse`.
- Check recursively extensions' replace constraints.
- The ``readOnly``, ``immutable`` and ``required`` constraints of a PATCH operation are checked
against the attribute its path resolves to, instead of against a literal field name match.
They used to be skipped whenever the two differed, so ``userName`` could be removed and a
path spelled ``GROUPS`` could write to a ``readOnly`` attribute, :rfc:`7643` §2.1 making
attribute names case-insensitive.
Removed
^^^^^^^
- ``Error.make_*_error()`` class methods, deprecated in 0.6.0. Use the matching
:class:`~scim2_models.SCIMException` subclass and its ``to_error()`` method instead.
- The ``ExternalReference`` and ``URIReference`` aliases, deprecated in 0.6.0. Use
:class:`~scim2_models.External` and :class:`~scim2_models.URI` instead.
- The ``Reference[Literal["X"]]`` syntax, deprecated in 0.6.0. Use ``Reference["X"]`` instead.
- Defining a model schema with a ``schemas`` default value, deprecated in 0.6.0. Use
``__schema__ = URN("...")`` instead. Note that ``__schema__`` only accepts valid URNs, while
the removed syntax silently ignored invalid ones.
Deprecated
^^^^^^^^^^
- ``Resource.get_by_schema`` and ``Resource.get_by_payload`` are deprecated in favor of
:func:`~scim2_models.get_model_by_schema` and :func:`~scim2_models.get_model_by_payload`.
Their ``resource_types`` parameter is named ``models`` in the new functions.
They will be removed in 0.8.0.
Performance
^^^^^^^^^^^
- Cached commonly used metadata of fields to ``__scim_info__``.
- Collapsed all scim context validators in :class:`~scim2_models.BaseModel` to one model validator.
- Collapsed serialization to one model serializer in :class:`~scim2_models.BaseModel`.
- Moved ``model_dump`` and ``model_dump_json`` to :class:`~scim2_models.BaseModel`.
- Cached ``_normalize_attribute_name``.
- Simplified ``normalize_attribute_names``.
PreviousNext