@@ -195,6 +195,19 @@ def __init__(
195195 self .check_response_status_codes = check_response_status_codes
196196 self .raise_scim_errors = raise_scim_errors
197197
198+ @property
199+ def _etag_supported (self ) -> bool :
200+ spc = self .service_provider_config
201+ return bool (spc and spc .etag and spc .etag .supported )
202+
203+ def _set_if_match (self , req : RequestPayload , resource : Resource ) -> None :
204+ """Add ``If-Match`` header to the request if the server supports ETags."""
205+ if not self ._etag_supported :
206+ return
207+ if resource .meta and resource .meta .version :
208+ headers = req .request_kwargs .setdefault ("headers" , {})
209+ headers .setdefault ("If-Match" , resource .meta .version )
210+
198211 def get_resource_model (self , name : str ) -> type [Resource ] | None :
199212 """Get a registered model by its name or its schema."""
200213 for resource_model in self .resource_models :
@@ -515,8 +528,7 @@ def _prepare_search_request(
515528
516529 def _prepare_delete_request (
517530 self ,
518- resource_model : type [Resource ],
519- id : str ,
531+ resource : Resource ,
520532 expected_status_codes : list [int ] | None = None ,
521533 ** kwargs ,
522534 ) -> RequestPayload :
@@ -525,9 +537,13 @@ def _prepare_delete_request(
525537 request_kwargs = kwargs ,
526538 )
527539
540+ resource_model = type (resource )
528541 self ._check_resource_model (resource_model )
529- delete_url = self .resource_endpoint (resource_model ) + f"/{ id } "
542+ if not resource .id :
543+ raise SCIMRequestError ("Resource must have an id" , source = resource )
544+ delete_url = self .resource_endpoint (resource_model ) + f"/{ resource .id } "
530545 req .url = req .request_kwargs .pop ("url" , delete_url )
546+ self ._set_if_match (req , resource )
531547 return req
532548
533549 def _prepare_replace_request (
@@ -575,6 +591,7 @@ def _prepare_replace_request(
575591 raise SCIMRequestError ("Resource must have an id" , source = resource )
576592
577593 req .expected_types = [resource .__class__ ]
594+ self ._set_if_match (req , resource )
578595 req .payload = resource .model_dump (
579596 scim_ctx = Context .RESOURCE_REPLACEMENT_REQUEST
580597 )
@@ -586,29 +603,15 @@ def _prepare_replace_request(
586603
587604 def _prepare_patch_request (
588605 self ,
589- resource_model : type [ResourceT ],
590- id : str ,
591- patch_op : PatchOp [ResourceT ] | dict ,
606+ resource : Resource ,
607+ patch_op : PatchOp | dict ,
592608 check_request_payload : bool | None = None ,
593609 expected_status_codes : list [int ] | None = None ,
594610 ** kwargs ,
595611 ) -> RequestPayload :
596- """Prepare a PATCH request payload.
597-
598- :param resource_model: The resource type to modify (e.g., User, Group).
599- :param id: The resource ID.
600- :param patch_op: A PatchOp instance parameterized with the same resource type as resource_model
601- (e.g., PatchOp[User] when resource_model is User), or a dict representation.
602- :param check_request_payload: If :data:`False`, :code:`patch_op` is expected to be a dict
603- that will be passed as-is in the request. This value can be
604- overwritten in methods.
605- :param expected_status_codes: List of HTTP status codes expected for this request.
606- :param raise_scim_errors: If :data:`True` and the server returned an
607- :class:`~scim2_models.Error` object during a request, a
608- :class:`~scim2_client.SCIMResponseErrorObject` exception will be raised.
609- :param kwargs: Additional request parameters.
610- :return: The prepared request payload.
611- """
612+ """Prepare a PATCH request payload."""
613+ resource_model = type (resource )
614+ id = resource .id
612615 req = RequestPayload (
613616 expected_status_codes = expected_status_codes ,
614617 request_kwargs = kwargs ,
@@ -644,12 +647,12 @@ def _prepare_patch_request(
644647 )
645648
646649 req .expected_types = [resource_model ]
650+ self ._set_if_match (req , resource )
647651 return req
648652
649653 def modify (
650654 self ,
651- resource_model : type [ResourceT ],
652- id : str ,
655+ resource : ResourceT ,
653656 patch_op : PatchOp [ResourceT ] | dict ,
654657 ** kwargs ,
655658 ) -> ResourceT | Error | dict | None :
@@ -858,18 +861,19 @@ def search(
858861
859862 def delete (
860863 self ,
861- resource_model : type ,
862- id : str ,
864+ resource : Resource ,
863865 check_response_payload : bool | None = None ,
864866 expected_status_codes : list [int ]
865867 | None = SCIMClient .DELETION_RESPONSE_STATUS_CODES ,
866868 raise_scim_errors : bool | None = None ,
867869 ** kwargs ,
868870 ) -> Error | dict | None :
869- """Perform a DELETE request to create , as defined in :rfc:`RFC7644 §3.6 <7644#section-3.6>`.
871+ """Perform a DELETE request, as defined in :rfc:`RFC7644 §3.6 <7644#section-3.6>`.
870872
871- :param resource_model: The type of the resource to delete.
872- :param id: The type id the resource to delete.
873+ :param resource: The resource to delete. If the server supports ETags
874+ and the resource has ``meta.version``, an ``If-Match`` header is sent.
875+ :param resource_model: Deprecated. The type of the resource to delete.
876+ :param id: Deprecated. The id of the resource to delete.
873877 :param check_response_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_response_payload`.
874878 :param expected_status_codes: The list of expected status codes form the response.
875879 If :data:`None` any status code is accepted.
@@ -884,11 +888,10 @@ def delete(
884888 :usage:
885889
886890 .. code-block:: python
887- :caption: Deleting an ` User` which `id` is `foobar`
891+ :caption: Deleting a User resource
888892
889- from scim2_models import User, SearchRequest
890-
891- response = scim.delete(User, "foobar")
893+ user = scim.query(User, "foobar")
894+ response = scim.delete(resource=user)
892895 # 'response' may be None, or an Error object
893896 """
894897 raise NotImplementedError ()
@@ -941,8 +944,7 @@ def replace(
941944
942945 def modify (
943946 self ,
944- resource_model : type [ResourceT ],
945- id : str ,
947+ resource : ResourceT ,
946948 patch_op : PatchOp [ResourceT ] | dict ,
947949 check_request_payload : bool | None = None ,
948950 check_response_payload : bool | None = None ,
@@ -953,11 +955,11 @@ def modify(
953955 ) -> ResourceT | Error | dict | None :
954956 """Perform a PATCH request to modify a resource, as defined in :rfc:`RFC7644 §3.5.2 <7644#section-3.5.2>`.
955957
956- :param resource_model : The type of the resource to modify.
957- :param id: The id of the resource to modify .
958+ :param resource : The resource to modify. If the server supports ETags
959+ and the resource has ``meta.version``, an ``If-Match`` header is sent .
958960 :param patch_op: The :class:`~scim2_models.PatchOp` object describing the modifications.
959- Must be parameterized with the same resource type as ``resource_model``
960- (e.g., :code:`PatchOp[User]` when ``resource_model`` is :code:`User`) .
961+ :param resource_model: Deprecated. The type of the resource to modify.
962+ :param id: Deprecated. The id of the resource to modify .
961963 :param check_request_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_request_payload`.
962964 :param check_response_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_response_payload`.
963965 :param expected_status_codes: The list of expected status codes form the response.
@@ -978,11 +980,12 @@ def modify(
978980
979981 from scim2_models import User, PatchOp, PatchOperation
980982
983+ user = scim.query(User, "my-user-id")
981984 operation = PatchOperation(
982985 op="replace", path="displayName", value="New Display Name"
983986 )
984987 patch_op = PatchOp[User](operations=[operation])
985- response = scim.modify(User, "my- user-id", patch_op)
988+ response = scim.modify(resource= user, patch_op= patch_op)
986989 # 'response' may be a User, None, or an Error object
987990
988991 .. tip::
@@ -1193,18 +1196,19 @@ async def search(
11931196
11941197 async def delete (
11951198 self ,
1196- resource_model : type ,
1197- id : str ,
1199+ resource : Resource ,
11981200 check_response_payload : bool | None = None ,
11991201 expected_status_codes : list [int ]
12001202 | None = SCIMClient .DELETION_RESPONSE_STATUS_CODES ,
12011203 raise_scim_errors : bool | None = None ,
12021204 ** kwargs ,
12031205 ) -> Error | dict | None :
1204- """Perform a DELETE request to create , as defined in :rfc:`RFC7644 §3.6 <7644#section-3.6>`.
1206+ """Perform a DELETE request, as defined in :rfc:`RFC7644 §3.6 <7644#section-3.6>`.
12051207
1206- :param resource_model: The type of the resource to delete.
1207- :param id: The type id the resource to delete.
1208+ :param resource: The resource to delete. If the server supports ETags
1209+ and the resource has ``meta.version``, an ``If-Match`` header is sent.
1210+ :param resource_model: Deprecated. The type of the resource to delete.
1211+ :param id: Deprecated. The id of the resource to delete.
12081212 :param check_response_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_response_payload`.
12091213 :param expected_status_codes: The list of expected status codes form the response.
12101214 If :data:`None` any status code is accepted.
@@ -1219,11 +1223,10 @@ async def delete(
12191223 :usage:
12201224
12211225 .. code-block:: python
1222- :caption: Deleting an `User` which `id` is `foobar`
1223-
1224- from scim2_models import User, SearchRequest
1226+ :caption: Deleting a User resource
12251227
1226- response = scim.delete(User, "foobar")
1228+ user = scim.query(User, "foobar")
1229+ response = await scim.delete(resource=user)
12271230 # 'response' may be None, or an Error object
12281231 """
12291232 raise NotImplementedError ()
@@ -1276,8 +1279,7 @@ async def replace(
12761279
12771280 async def modify (
12781281 self ,
1279- resource_model : type [ResourceT ],
1280- id : str ,
1282+ resource : ResourceT ,
12811283 patch_op : PatchOp [ResourceT ] | dict ,
12821284 check_request_payload : bool | None = None ,
12831285 check_response_payload : bool | None = None ,
@@ -1288,11 +1290,11 @@ async def modify(
12881290 ) -> ResourceT | Error | dict | None :
12891291 """Perform a PATCH request to modify a resource, as defined in :rfc:`RFC7644 §3.5.2 <7644#section-3.5.2>`.
12901292
1291- :param resource_model : The type of the resource to modify.
1292- :param id: The id of the resource to modify .
1293+ :param resource : The resource to modify. If the server supports ETags
1294+ and the resource has ``meta.version``, an ``If-Match`` header is sent .
12931295 :param patch_op: The :class:`~scim2_models.PatchOp` object describing the modifications.
1294- Must be parameterized with the same resource type as ``resource_model``
1295- (e.g., :code:`PatchOp[User]` when ``resource_model`` is :code:`User`) .
1296+ :param resource_model: Deprecated. The type of the resource to modify.
1297+ :param id: Deprecated. The id of the resource to modify .
12961298 :param check_request_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_request_payload`.
12971299 :param check_response_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_response_payload`.
12981300 :param expected_status_codes: The list of expected status codes form the response.
@@ -1313,11 +1315,12 @@ async def modify(
13131315
13141316 from scim2_models import User, PatchOp, PatchOperation
13151317
1318+ user = scim.query(User, "my-user-id")
13161319 operation = PatchOperation(
13171320 op="replace", path="displayName", value="New Display Name"
13181321 )
13191322 patch_op = PatchOp[User](operations=[operation])
1320- response = await scim.modify(User, "my- user-id", patch_op)
1323+ response = await scim.modify(resource= user, patch_op= patch_op)
13211324 # 'response' may be a User, None, or an Error object
13221325
13231326 .. tip::
0 commit comments