Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ Added
sends. 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, instead of its ``attributes`` and
``excludedAttributes`` spelled out one by one. A :class:`~scim2_models.SearchRequest` is one,
so a server answering ``POST /.search`` passes the request it received. :issue:`141`
- lark is a new dependency.

Changed
Expand Down Expand Up @@ -75,6 +80,14 @@ Removed
- ``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 targeting an attribute of an extension answers for the constraints that
Expand Down
11 changes: 8 additions & 3 deletions doc/how-to/validate-and-serialize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,18 @@ Return only the attributes a client asked for
---------------------------------------------

A client narrows a response with the ``attributes`` or ``excludedAttributes`` query parameters of
:rfc:`RFC7644 §3.9 <7644#section-3.9>`. Both take SCIM attribute names, and go straight to the
dump method:
:rfc:`RFC7644 §3.9 <7644#section-3.9>`. Read them into a
:class:`~scim2_models.ResponseParameters` and hand it to the dump method:

.. doctest::

>>> from scim2_models import ResponseParameters
>>> user.id = "2819c223-7f76-453a-919d-413861904646"
>>> user.display_name = "Babs Jensen"
>>> user.title = "Manager"
>>> user.model_dump(
... scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
... attributes=["userName"],
... response_parameters=ResponseParameters(attributes=["userName"]),
... ) # doctest: +NORMALIZE_WHITESPACE
{'schemas': ['urn:ietf:params:scim:schemas:core:2.0:User'],
'id': '2819c223-7f76-453a-919d-413861904646',
Expand All @@ -83,6 +84,10 @@ attribute annotated :attr:`Returned.always <scim2_models.Returned.always>` canno
Symmetrically, an attribute annotated :attr:`Returned.never <scim2_models.Returned.never>`, such
as :attr:`User.password <scim2_models.User.password>`, never appears whatever a client asks.

A :class:`~scim2_models.SearchRequest` is a :class:`~scim2_models.ResponseParameters`, so a server
answering ``POST /.search`` passes the request it received rather than spelling the two parameters
out.

Replace a stored resource
-------------------------

Expand Down
18 changes: 6 additions & 12 deletions doc/integrations/_examples/django_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ def get(self, request, app_record):
return SCIMJsonResponse(
scim_user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)
)

Expand Down Expand Up @@ -211,8 +210,7 @@ def put(self, request, app_record):
return SCIMJsonResponse(
response_user.model_dump(
scim_ctx=Context.RESOURCE_REPLACEMENT_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)
)

Expand Down Expand Up @@ -240,8 +238,7 @@ def patch(self, request, app_record):
return SCIMJsonResponse(
scim_user.model_dump(
scim_ctx=Context.RESOURCE_PATCH_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)
)

Expand Down Expand Up @@ -276,8 +273,7 @@ def users_response(request, req, scim_ctx):
return SCIMJsonResponse(
response.model_dump(
scim_ctx=scim_ctx,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)
)

Expand Down Expand Up @@ -324,8 +320,7 @@ def post(self, request):
return SCIMJsonResponse(
response_user.model_dump(
scim_ctx=Context.RESOURCE_CREATION_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
),
status=HTTPStatus.CREATED,
)
Expand Down Expand Up @@ -394,8 +389,7 @@ def post(self, request):
return SCIMJsonResponse(
response.model_dump(
scim_ctx=Context.SEARCH_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)
)

Expand Down
18 changes: 6 additions & 12 deletions doc/integrations/_examples/fastapi_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,7 @@ async def get_user(
return SCIMResponse(
scim_user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
),
)
# -- get-user-end --
Expand All @@ -171,8 +170,7 @@ async def patch_user(
return SCIMResponse(
response_user.model_dump(
scim_ctx=Context.RESOURCE_PATCH_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
),
)
# -- patch-user-end --
Expand All @@ -198,8 +196,7 @@ async def replace_user(
return SCIMResponse(
response_user.model_dump(
scim_ctx=Context.RESOURCE_REPLACEMENT_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
),
)
# -- put-user-end --
Expand Down Expand Up @@ -245,8 +242,7 @@ def users_response(request, req, scim_ctx):
return SCIMResponse(
response.model_dump(
scim_ctx=scim_ctx,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
),
)

Expand Down Expand Up @@ -302,8 +298,7 @@ async def search_root(
return SCIMResponse(
response.model_dump(
scim_ctx=Context.SEARCH_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
),
)
# -- search-root-end --
Expand All @@ -324,8 +319,7 @@ async def create_user(
return SCIMResponse(
response_user.model_dump(
scim_ctx=Context.RESOURCE_CREATION_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
),
status_code=HTTPStatus.CREATED,
)
Expand Down
18 changes: 6 additions & 12 deletions doc/integrations/_examples/flask_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,7 @@ def get_user(app_record):
scim_user = to_scim_user(app_record, resource_location(app_record))
return scim_user.model_dump(
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)
# -- get-user-end --

Expand All @@ -165,8 +164,7 @@ def patch_user(app_record):

return scim_user.model_dump(
scim_ctx=Context.RESOURCE_PATCH_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)
# -- patch-user-end --

Expand All @@ -189,8 +187,7 @@ def replace_user(app_record):
response_user = to_scim_user(updated_record, resource_location(updated_record))
return response_user.model_dump(
scim_ctx=Context.RESOURCE_REPLACEMENT_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)
# -- put-user-end --

Expand Down Expand Up @@ -231,8 +228,7 @@ def users_response(req, scim_ctx):
)
return response.model_dump(
scim_ctx=scim_ctx,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)


Expand Down Expand Up @@ -286,8 +282,7 @@ def search_root():
)
return response.model_dump(
scim_ctx=Context.SEARCH_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
)
# -- search-root-end --

Expand All @@ -308,8 +303,7 @@ def create_user():
return (
response_user.model_dump(
scim_ctx=Context.RESOURCE_CREATION_RESPONSE,
attributes=req.attributes,
excluded_attributes=req.excluded_attributes,
response_parameters=req,
),
HTTPStatus.CREATED,
)
Expand Down
4 changes: 2 additions & 2 deletions doc/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ attributes:

.. doctest::

>>> from scim2_models import Context, User
>>> from scim2_models import Context, ResponseParameters, User
>>> payload = {
... "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
... "id": "client-supplied",
Expand All @@ -78,7 +78,7 @@ projection requested by a client:
>>> user.display_name = "Babs Jensen"
>>> response = user.model_dump(
... scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
... excluded_attributes=["displayName"],
... response_parameters=ResponseParameters(excluded_attributes=["displayName"]),
... )
>>> response["id"]
'2819c223-7f76-453a-919d-413861904646'
Expand Down
72 changes: 72 additions & 0 deletions scim2_models/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import warnings
from collections.abc import Mapping
from inspect import isclass
from types import MappingProxyType
Expand Down Expand Up @@ -40,6 +41,7 @@
from scim2_models.utils import _to_camel

if TYPE_CHECKING:
from scim2_models.messages.response_parameters import ResponseParameters
from scim2_models.path import Path


Expand Down Expand Up @@ -846,10 +848,47 @@ def _prepare_model_dump(

return kwargs

@staticmethod
def _attribute_selection(
response_parameters: "ResponseParameters[Any] | None",
attributes: list["str | Path[Any]"] | None,
excluded_attributes: list["str | Path[Any]"] | None,
) -> tuple[list["str | Path[Any]"] | None, list["str | Path[Any]"] | None]:
"""Read the attribute selection of a dump, from either spelling."""
if response_parameters is None:
if attributes is not None or excluded_attributes is not None:
warnings.warn(
"The 'attributes' and 'excluded_attributes' parameters are "
"deprecated, pass a ResponseParameters as 'response_parameters' "
"instead. Will be removed in 0.9.0.",
DeprecationWarning,
stacklevel=3,
)
return attributes, excluded_attributes

if attributes is not None or excluded_attributes is not None:
raise TypeError(
"Cannot pass both 'response_parameters' and "
"'attributes' or 'excluded_attributes'"
)
# les listes de ResponseParameters sont invariantes, on les recopie élargies
selected: list[str | Path[Any]] | None = (
list(response_parameters.attributes)
if response_parameters.attributes is not None
else None
)
excluded: list[str | Path[Any]] | None = (
list(response_parameters.excluded_attributes)
if response_parameters.excluded_attributes is not None
else None
)
return selected, excluded

def model_dump(
self,
*args: Any,
scim_ctx: Context | None = Context.DEFAULT,
response_parameters: "ResponseParameters[Any] | None" = None,
attributes: list["str | Path[Any]"] | None = None,
excluded_attributes: list["str | Path[Any]"] | None = None,
scim_policy: ScimPolicy | None = None,
Expand All @@ -860,15 +899,31 @@ def model_dump(
:param scim_ctx: If a SCIM context is passed, some default values of
Pydantic :code:`BaseModel.model_dump` are tuned to generate valid SCIM
messages. Pass :data:`None` to get the default Pydantic behavior.
:param response_parameters: The
:class:`~scim2_models.ResponseParameters` a client sent, whose
``attributes`` and ``excludedAttributes`` select what the dump
carries. A :class:`~scim2_models.SearchRequest` is one, so a server
may pass the request it received.
:param attributes: A multi-valued list of strings indicating the names of resource
attributes to return in the response, overriding the set of attributes that
would be returned by default. Invalid values are ignored.

.. deprecated:: 0.8.0
Pass a :class:`~scim2_models.ResponseParameters` as
*response_parameters* instead. Will be removed in 0.9.0.
:param excluded_attributes: A multi-valued list of strings indicating the names of resource
attributes to be removed from the default set of attributes to return. Invalid values are ignored.

.. deprecated:: 0.8.0
Pass a :class:`~scim2_models.ResponseParameters` as
*response_parameters* instead. Will be removed in 0.9.0.
:param scim_policy: The :class:`~scim2_models.ScimPolicy` the
serialization runs under. Defaults to the strict reading of the
specification.
"""
attributes, excluded_attributes = self._attribute_selection(
response_parameters, attributes, excluded_attributes
)
dump_kwargs = self._prepare_model_dump(
scim_ctx,
attributes=attributes,
Expand All @@ -884,6 +939,7 @@ def model_dump_json(
self,
*args: Any,
scim_ctx: Context | None = Context.DEFAULT,
response_parameters: "ResponseParameters[Any] | None" = None,
attributes: list["str | Path[Any]"] | None = None,
excluded_attributes: list["str | Path[Any]"] | None = None,
scim_policy: ScimPolicy | None = None,
Expand All @@ -894,15 +950,31 @@ def model_dump_json(
:param scim_ctx: If a SCIM context is passed, some default values of
Pydantic :code:`BaseModel.model_dump` are tuned to generate valid SCIM
messages. Pass :data:`None` to get the default Pydantic behavior.
:param response_parameters: The
:class:`~scim2_models.ResponseParameters` a client sent, whose
``attributes`` and ``excludedAttributes`` select what the dump
carries. A :class:`~scim2_models.SearchRequest` is one, so a server
may pass the request it received.
:param attributes: A multi-valued list of strings indicating the names of resource
attributes to return in the response, overriding the set of attributes that
would be returned by default. Invalid values are ignored.

.. deprecated:: 0.8.0
Pass a :class:`~scim2_models.ResponseParameters` as
*response_parameters* instead. Will be removed in 0.9.0.
:param excluded_attributes: A multi-valued list of strings indicating the names of resource
attributes to be removed from the default set of attributes to return. Invalid values are ignored.

.. deprecated:: 0.8.0
Pass a :class:`~scim2_models.ResponseParameters` as
*response_parameters* instead. Will be removed in 0.9.0.
:param scim_policy: The :class:`~scim2_models.ScimPolicy` the
serialization runs under. Defaults to the strict reading of the
specification.
"""
attributes, excluded_attributes = self._attribute_selection(
response_parameters, attributes, excluded_attributes
)
dump_kwargs = self._prepare_model_dump(
scim_ctx,
attributes=attributes,
Expand Down
Loading
Loading