scim2-client 0.2.2__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scim2_client/__init__.py +4 -2
- scim2_client/client.py +556 -248
- scim2_client/engines/__init__.py +0 -0
- scim2_client/engines/httpx.py +430 -0
- scim2_client/engines/werkzeug.py +245 -0
- scim2_client/errors.py +6 -9
- {scim2_client-0.2.2.dist-info → scim2_client-0.3.1.dist-info}/METADATA +11 -7
- scim2_client-0.3.1.dist-info/RECORD +11 -0
- {scim2_client-0.2.2.dist-info → scim2_client-0.3.1.dist-info}/WHEEL +1 -1
- scim2_client-0.2.2.dist-info/RECORD +0 -8
- {scim2_client-0.2.2.dist-info → scim2_client-0.3.1.dist-info}/licenses/LICENSE.md +0 -0
scim2_client/client.py
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import json.decoder
|
|
3
1
|
import sys
|
|
2
|
+
from dataclasses import dataclass
|
|
4
3
|
from typing import Optional
|
|
5
4
|
from typing import Union
|
|
6
5
|
|
|
7
|
-
from httpx import Client
|
|
8
|
-
from httpx import RequestError
|
|
9
|
-
from httpx import Response
|
|
10
6
|
from pydantic import ValidationError
|
|
11
7
|
from scim2_models import AnyResource
|
|
12
8
|
from scim2_models import Context
|
|
@@ -19,16 +15,14 @@ from scim2_models import Schema
|
|
|
19
15
|
from scim2_models import SearchRequest
|
|
20
16
|
from scim2_models import ServiceProviderConfig
|
|
21
17
|
|
|
22
|
-
from .errors import
|
|
23
|
-
from .errors import
|
|
24
|
-
from .errors import
|
|
25
|
-
from .errors import
|
|
26
|
-
from .errors import
|
|
27
|
-
from .errors import
|
|
28
|
-
from .errors import
|
|
29
|
-
from .errors import
|
|
30
|
-
from .errors import UnexpectedContentType
|
|
31
|
-
from .errors import UnexpectedStatusCode
|
|
18
|
+
from scim2_client.errors import RequestPayloadValidationError
|
|
19
|
+
from scim2_client.errors import ResponsePayloadValidationError
|
|
20
|
+
from scim2_client.errors import SCIMClientError
|
|
21
|
+
from scim2_client.errors import SCIMRequestError
|
|
22
|
+
from scim2_client.errors import SCIMResponseError
|
|
23
|
+
from scim2_client.errors import SCIMResponseErrorObject
|
|
24
|
+
from scim2_client.errors import UnexpectedContentType
|
|
25
|
+
from scim2_client.errors import UnexpectedStatusCode
|
|
32
26
|
|
|
33
27
|
BASE_HEADERS = {
|
|
34
28
|
"Accept": "application/scim+json",
|
|
@@ -36,11 +30,23 @@ BASE_HEADERS = {
|
|
|
36
30
|
}
|
|
37
31
|
|
|
38
32
|
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
@dataclass
|
|
34
|
+
class RequestPayload:
|
|
35
|
+
request_kwargs: dict
|
|
36
|
+
url: Optional[str] = None
|
|
37
|
+
payload: Optional[dict] = None
|
|
38
|
+
expected_types: Optional[list[type[Resource]]] = None
|
|
39
|
+
expected_status_codes: Optional[list[int]] = None
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
|
|
42
|
+
class BaseSCIMClient:
|
|
43
|
+
"""The base model for request clients.
|
|
44
|
+
|
|
45
|
+
It goal is to parse the requests and responses and check if they comply with the SCIM specifications.
|
|
46
|
+
|
|
47
|
+
This class can be inherited and used as a basis for request engine integration.
|
|
48
|
+
|
|
49
|
+
:param resource_models: A tuple of :class:`~scim2_models.Resource` types expected to be handled by the SCIM client.
|
|
44
50
|
If a request payload describe a resource that is not in this list, an exception will be raised.
|
|
45
51
|
|
|
46
52
|
.. note::
|
|
@@ -128,44 +134,47 @@ class SCIMClient:
|
|
|
128
134
|
:rfc:`RFC7644 §3.12 <7644#section-3.12>`.
|
|
129
135
|
"""
|
|
130
136
|
|
|
131
|
-
def __init__(
|
|
132
|
-
self
|
|
133
|
-
|
|
134
|
-
self.client = client
|
|
135
|
-
self.resource_types = tuple(
|
|
136
|
-
set(resource_types or []) | {ResourceType, Schema, ServiceProviderConfig}
|
|
137
|
+
def __init__(self, resource_models: Optional[tuple[type[Resource]]] = None):
|
|
138
|
+
self.resource_models = tuple(
|
|
139
|
+
set(resource_models or []) | {ResourceType, Schema, ServiceProviderConfig}
|
|
137
140
|
)
|
|
138
141
|
|
|
139
|
-
def
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
def check_resource_model(
|
|
143
|
+
self, resource_model: type[Resource], payload=None
|
|
144
|
+
) -> None:
|
|
145
|
+
if resource_model not in self.resource_models:
|
|
146
|
+
raise SCIMRequestError(
|
|
147
|
+
f"Unknown resource type: '{resource_model}'", source=payload
|
|
148
|
+
)
|
|
142
149
|
|
|
143
|
-
def resource_endpoint(self,
|
|
144
|
-
if
|
|
150
|
+
def resource_endpoint(self, resource_model: Optional[type[Resource]]) -> str:
|
|
151
|
+
if resource_model is None:
|
|
145
152
|
return "/"
|
|
146
153
|
|
|
147
154
|
# This one takes no final 's'
|
|
148
|
-
if
|
|
155
|
+
if resource_model is ServiceProviderConfig:
|
|
149
156
|
return "/ServiceProviderConfig"
|
|
150
157
|
|
|
151
158
|
try:
|
|
152
|
-
first_bracket_index =
|
|
153
|
-
root_name =
|
|
159
|
+
first_bracket_index = resource_model.__name__.index("[")
|
|
160
|
+
root_name = resource_model.__name__[:first_bracket_index]
|
|
154
161
|
except ValueError:
|
|
155
|
-
root_name =
|
|
162
|
+
root_name = resource_model.__name__
|
|
156
163
|
return f"/{root_name}s"
|
|
157
164
|
|
|
158
165
|
def check_response(
|
|
159
166
|
self,
|
|
160
|
-
|
|
167
|
+
payload: Optional[dict],
|
|
168
|
+
status_code: int,
|
|
169
|
+
headers: dict,
|
|
161
170
|
expected_status_codes: Optional[list[int]] = None,
|
|
162
171
|
expected_types: Optional[list[type[Resource]]] = None,
|
|
163
172
|
check_response_payload: bool = True,
|
|
164
173
|
raise_scim_errors: bool = True,
|
|
165
174
|
scim_ctx: Optional[Context] = None,
|
|
166
175
|
) -> Union[Error, None, dict, type[Resource]]:
|
|
167
|
-
if expected_status_codes and
|
|
168
|
-
raise UnexpectedStatusCode(
|
|
176
|
+
if expected_status_codes and status_code not in expected_status_codes:
|
|
177
|
+
raise UnexpectedStatusCode(status_code)
|
|
169
178
|
|
|
170
179
|
# Interoperability considerations: The "application/scim+json" media
|
|
171
180
|
# type is intended to identify JSON structure data that conforms to
|
|
@@ -173,24 +182,21 @@ class SCIMClient:
|
|
|
173
182
|
# SCIM are known to informally use "application/json".
|
|
174
183
|
# https://datatracker.ietf.org/doc/html/rfc7644.html#section-8.1
|
|
175
184
|
|
|
176
|
-
actual_content_type =
|
|
185
|
+
actual_content_type = headers.get("content-type", "").split(";").pop(0)
|
|
177
186
|
expected_response_content_types = ("application/scim+json", "application/json")
|
|
178
187
|
if actual_content_type not in expected_response_content_types:
|
|
179
|
-
raise UnexpectedContentType(
|
|
188
|
+
raise UnexpectedContentType(content_type=actual_content_type)
|
|
180
189
|
|
|
181
190
|
# In addition to returning an HTTP response code, implementers MUST return
|
|
182
191
|
# the errors in the body of the response in a JSON format
|
|
183
192
|
# https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.12
|
|
184
193
|
|
|
185
194
|
no_content_status_codes = [204, 205]
|
|
186
|
-
if
|
|
195
|
+
if status_code in no_content_status_codes:
|
|
187
196
|
response_payload = None
|
|
188
197
|
|
|
189
198
|
else:
|
|
190
|
-
|
|
191
|
-
response_payload = response.json()
|
|
192
|
-
except json.decoder.JSONDecodeError as exc:
|
|
193
|
-
raise UnexpectedContentFormat(source=response) from exc
|
|
199
|
+
response_payload = payload
|
|
194
200
|
|
|
195
201
|
if not check_response_payload:
|
|
196
202
|
return response_payload
|
|
@@ -201,7 +207,7 @@ class SCIMClient:
|
|
|
201
207
|
):
|
|
202
208
|
error = Error.model_validate(response_payload)
|
|
203
209
|
if raise_scim_errors:
|
|
204
|
-
raise SCIMResponseErrorObject(source=error)
|
|
210
|
+
raise SCIMResponseErrorObject(obj=error.detail, source=error)
|
|
205
211
|
return error
|
|
206
212
|
|
|
207
213
|
if not expected_types:
|
|
@@ -221,22 +227,233 @@ class SCIMClient:
|
|
|
221
227
|
f"Expected type {expected} but got undefined object with no schema"
|
|
222
228
|
)
|
|
223
229
|
|
|
224
|
-
raise SCIMResponseError(message
|
|
230
|
+
raise SCIMResponseError(message)
|
|
225
231
|
|
|
226
232
|
try:
|
|
227
233
|
return actual_type.model_validate(response_payload, scim_ctx=scim_ctx)
|
|
228
234
|
except ValidationError as exc:
|
|
229
|
-
scim_exc = ResponsePayloadValidationError(
|
|
235
|
+
scim_exc = ResponsePayloadValidationError()
|
|
230
236
|
if sys.version_info >= (3, 11): # pragma: no cover
|
|
231
237
|
scim_exc.add_note(str(exc))
|
|
232
238
|
raise scim_exc from exc
|
|
233
239
|
|
|
240
|
+
def prepare_create_request(
|
|
241
|
+
self,
|
|
242
|
+
resource: Union[AnyResource, dict],
|
|
243
|
+
check_request_payload: bool = True,
|
|
244
|
+
expected_status_codes: Optional[list[int]] = None,
|
|
245
|
+
raise_scim_errors: bool = True,
|
|
246
|
+
**kwargs,
|
|
247
|
+
) -> RequestPayload:
|
|
248
|
+
req = RequestPayload(
|
|
249
|
+
expected_status_codes=expected_status_codes,
|
|
250
|
+
request_kwargs=kwargs,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if not check_request_payload:
|
|
254
|
+
req.payload = resource
|
|
255
|
+
req.url = req.request_kwargs.pop("url", None)
|
|
256
|
+
|
|
257
|
+
else:
|
|
258
|
+
if isinstance(resource, Resource):
|
|
259
|
+
resource_model = resource.__class__
|
|
260
|
+
|
|
261
|
+
else:
|
|
262
|
+
resource_model = Resource.get_by_payload(self.resource_models, resource)
|
|
263
|
+
if not resource_model:
|
|
264
|
+
raise SCIMRequestError(
|
|
265
|
+
"Cannot guess resource type from the payload"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
resource = resource_model.model_validate(resource)
|
|
270
|
+
except ValidationError as exc:
|
|
271
|
+
scim_validation_exc = RequestPayloadValidationError(source=resource)
|
|
272
|
+
if sys.version_info >= (3, 11): # pragma: no cover
|
|
273
|
+
scim_validation_exc.add_note(str(exc))
|
|
274
|
+
raise scim_validation_exc from exc
|
|
275
|
+
|
|
276
|
+
self.check_resource_model(resource_model, resource)
|
|
277
|
+
req.expected_types = [resource.__class__]
|
|
278
|
+
req.url = req.request_kwargs.pop(
|
|
279
|
+
"url", self.resource_endpoint(resource_model)
|
|
280
|
+
)
|
|
281
|
+
req.payload = resource.model_dump(
|
|
282
|
+
scim_ctx=Context.RESOURCE_CREATION_REQUEST
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return req
|
|
286
|
+
|
|
287
|
+
def prepare_query_request(
|
|
288
|
+
self,
|
|
289
|
+
resource_model: Optional[type[Resource]] = None,
|
|
290
|
+
id: Optional[str] = None,
|
|
291
|
+
search_request: Optional[Union[SearchRequest, dict]] = None,
|
|
292
|
+
check_request_payload: bool = True,
|
|
293
|
+
expected_status_codes: Optional[list[int]] = None,
|
|
294
|
+
raise_scim_errors: bool = True,
|
|
295
|
+
**kwargs,
|
|
296
|
+
) -> RequestPayload:
|
|
297
|
+
req = RequestPayload(
|
|
298
|
+
expected_status_codes=expected_status_codes,
|
|
299
|
+
request_kwargs=kwargs,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if resource_model and check_request_payload:
|
|
303
|
+
self.check_resource_model(resource_model)
|
|
304
|
+
|
|
305
|
+
payload: Optional[SearchRequest]
|
|
306
|
+
if not check_request_payload:
|
|
307
|
+
payload = search_request
|
|
308
|
+
|
|
309
|
+
elif isinstance(search_request, SearchRequest):
|
|
310
|
+
payload = search_request.model_dump(
|
|
311
|
+
exclude_unset=True,
|
|
312
|
+
scim_ctx=Context.RESOURCE_QUERY_REQUEST,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
else:
|
|
316
|
+
payload = None
|
|
317
|
+
|
|
318
|
+
req.payload = payload
|
|
319
|
+
req.url = req.request_kwargs.pop("url", self.resource_endpoint(resource_model))
|
|
320
|
+
|
|
321
|
+
if resource_model is None:
|
|
322
|
+
req.expected_types = [
|
|
323
|
+
*self.resource_models,
|
|
324
|
+
ListResponse[Union[self.resource_models]],
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
elif resource_model == ServiceProviderConfig:
|
|
328
|
+
req.expected_types = [resource_model]
|
|
329
|
+
if id:
|
|
330
|
+
raise SCIMClientError("ServiceProviderConfig cannot have an id")
|
|
331
|
+
|
|
332
|
+
elif id:
|
|
333
|
+
req.expected_types = [resource_model]
|
|
334
|
+
req.url = f"{req.url}/{id}"
|
|
335
|
+
|
|
336
|
+
else:
|
|
337
|
+
req.expected_types = [ListResponse[resource_model]]
|
|
338
|
+
|
|
339
|
+
return req
|
|
340
|
+
|
|
341
|
+
def prepare_search_request(
|
|
342
|
+
self,
|
|
343
|
+
search_request: Optional[SearchRequest] = None,
|
|
344
|
+
check_request_payload: bool = True,
|
|
345
|
+
expected_status_codes: Optional[list[int]] = None,
|
|
346
|
+
raise_scim_errors: bool = True,
|
|
347
|
+
**kwargs,
|
|
348
|
+
) -> RequestPayload:
|
|
349
|
+
req = RequestPayload(
|
|
350
|
+
expected_status_codes=expected_status_codes,
|
|
351
|
+
request_kwargs=kwargs,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
if not check_request_payload:
|
|
355
|
+
req.payload = search_request
|
|
356
|
+
|
|
357
|
+
else:
|
|
358
|
+
req.payload = (
|
|
359
|
+
search_request.model_dump(
|
|
360
|
+
exclude_unset=True, scim_ctx=Context.RESOURCE_QUERY_RESPONSE
|
|
361
|
+
)
|
|
362
|
+
if search_request
|
|
363
|
+
else None
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
req.url = req.request_kwargs.pop("url", "/.search")
|
|
367
|
+
req.expected_types = [ListResponse[Union[self.resource_models]]]
|
|
368
|
+
return req
|
|
369
|
+
|
|
370
|
+
def prepare_delete_request(
|
|
371
|
+
self,
|
|
372
|
+
resource_model: type,
|
|
373
|
+
id: str,
|
|
374
|
+
expected_status_codes: Optional[list[int]] = None,
|
|
375
|
+
raise_scim_errors: bool = True,
|
|
376
|
+
**kwargs,
|
|
377
|
+
) -> RequestPayload:
|
|
378
|
+
req = RequestPayload(
|
|
379
|
+
expected_status_codes=expected_status_codes,
|
|
380
|
+
request_kwargs=kwargs,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
self.check_resource_model(resource_model)
|
|
384
|
+
delete_url = self.resource_endpoint(resource_model) + f"/{id}"
|
|
385
|
+
req.url = req.request_kwargs.pop("url", delete_url)
|
|
386
|
+
return req
|
|
387
|
+
|
|
388
|
+
def prepare_replace_request(
|
|
389
|
+
self,
|
|
390
|
+
resource: Union[AnyResource, dict],
|
|
391
|
+
check_request_payload: bool = True,
|
|
392
|
+
expected_status_codes: Optional[list[int]] = None,
|
|
393
|
+
raise_scim_errors: bool = True,
|
|
394
|
+
**kwargs,
|
|
395
|
+
) -> RequestPayload:
|
|
396
|
+
req = RequestPayload(
|
|
397
|
+
expected_status_codes=expected_status_codes,
|
|
398
|
+
request_kwargs=kwargs,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
if not check_request_payload:
|
|
402
|
+
req.payload = resource
|
|
403
|
+
req.url = kwargs.pop("url", None)
|
|
404
|
+
|
|
405
|
+
else:
|
|
406
|
+
if isinstance(resource, Resource):
|
|
407
|
+
resource_model = resource.__class__
|
|
408
|
+
|
|
409
|
+
else:
|
|
410
|
+
resource_model = Resource.get_by_payload(self.resource_models, resource)
|
|
411
|
+
if not resource_model:
|
|
412
|
+
raise SCIMRequestError(
|
|
413
|
+
"Cannot guess resource type from the payload",
|
|
414
|
+
source=resource,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
resource = resource_model.model_validate(resource)
|
|
419
|
+
except ValidationError as exc:
|
|
420
|
+
scim_validation_exc = RequestPayloadValidationError(source=resource)
|
|
421
|
+
if sys.version_info >= (3, 11): # pragma: no cover
|
|
422
|
+
scim_validation_exc.add_note(str(exc))
|
|
423
|
+
raise scim_validation_exc from exc
|
|
424
|
+
|
|
425
|
+
self.check_resource_model(resource_model, resource)
|
|
426
|
+
|
|
427
|
+
if not resource.id:
|
|
428
|
+
raise SCIMRequestError("Resource must have an id", source=resource)
|
|
429
|
+
|
|
430
|
+
req.expected_types = [resource.__class__]
|
|
431
|
+
req.payload = resource.model_dump(
|
|
432
|
+
scim_ctx=Context.RESOURCE_REPLACEMENT_REQUEST
|
|
433
|
+
)
|
|
434
|
+
req.url = req.request_kwargs.pop(
|
|
435
|
+
"url", self.resource_endpoint(resource.__class__) + f"/{resource.id}"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
return req
|
|
439
|
+
|
|
440
|
+
def modify(
|
|
441
|
+
self, resource: Union[AnyResource, dict], op: PatchOp, **kwargs
|
|
442
|
+
) -> Optional[Union[AnyResource, dict]]:
|
|
443
|
+
raise NotImplementedError()
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class BaseSyncSCIMClient(BaseSCIMClient):
|
|
447
|
+
"""Base class for synchronous request clients."""
|
|
448
|
+
|
|
234
449
|
def create(
|
|
235
450
|
self,
|
|
236
451
|
resource: Union[AnyResource, dict],
|
|
237
452
|
check_request_payload: bool = True,
|
|
238
453
|
check_response_payload: bool = True,
|
|
239
|
-
expected_status_codes: Optional[
|
|
454
|
+
expected_status_codes: Optional[
|
|
455
|
+
list[int]
|
|
456
|
+
] = BaseSCIMClient.CREATION_RESPONSE_STATUS_CODES,
|
|
240
457
|
raise_scim_errors: bool = True,
|
|
241
458
|
**kwargs,
|
|
242
459
|
) -> Union[AnyResource, Error, dict]:
|
|
@@ -277,58 +494,18 @@ class SCIMClient:
|
|
|
277
494
|
which value will excluded from the request payload, and which values are expected in
|
|
278
495
|
the response payload.
|
|
279
496
|
"""
|
|
280
|
-
|
|
281
|
-
payload = resource
|
|
282
|
-
url = kwargs.pop("url", None)
|
|
283
|
-
|
|
284
|
-
else:
|
|
285
|
-
if isinstance(resource, Resource):
|
|
286
|
-
resource_type = resource.__class__
|
|
287
|
-
|
|
288
|
-
else:
|
|
289
|
-
resource_type = Resource.get_by_payload(self.resource_types, resource)
|
|
290
|
-
if not resource_type:
|
|
291
|
-
raise SCIMRequestError(
|
|
292
|
-
"Cannot guess resource type from the payload"
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
try:
|
|
296
|
-
resource = resource_type.model_validate(resource)
|
|
297
|
-
except ValidationError as exc:
|
|
298
|
-
scim_validation_exc = RequestPayloadValidationError(source=resource)
|
|
299
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
300
|
-
scim_validation_exc.add_note(str(exc))
|
|
301
|
-
raise scim_validation_exc from exc
|
|
302
|
-
|
|
303
|
-
self.check_resource_type(resource_type)
|
|
304
|
-
url = kwargs.pop("url", self.resource_endpoint(resource_type))
|
|
305
|
-
payload = resource.model_dump(scim_ctx=Context.RESOURCE_CREATION_REQUEST)
|
|
306
|
-
|
|
307
|
-
try:
|
|
308
|
-
response = self.client.post(url, json=payload, **kwargs)
|
|
309
|
-
except RequestError as exc:
|
|
310
|
-
scim_network_exc = RequestNetworkError(source=payload)
|
|
311
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
312
|
-
scim_network_exc.add_note(str(exc))
|
|
313
|
-
raise scim_network_exc from exc
|
|
314
|
-
|
|
315
|
-
return self.check_response(
|
|
316
|
-
response=response,
|
|
317
|
-
expected_status_codes=expected_status_codes,
|
|
318
|
-
expected_types=([resource.__class__] if check_request_payload else None),
|
|
319
|
-
check_response_payload=check_response_payload,
|
|
320
|
-
raise_scim_errors=raise_scim_errors,
|
|
321
|
-
scim_ctx=Context.RESOURCE_CREATION_RESPONSE,
|
|
322
|
-
)
|
|
497
|
+
raise NotImplementedError()
|
|
323
498
|
|
|
324
499
|
def query(
|
|
325
500
|
self,
|
|
326
|
-
|
|
501
|
+
resource_model: Optional[type[Resource]] = None,
|
|
327
502
|
id: Optional[str] = None,
|
|
328
503
|
search_request: Optional[Union[SearchRequest, dict]] = None,
|
|
329
504
|
check_request_payload: bool = True,
|
|
330
505
|
check_response_payload: bool = True,
|
|
331
|
-
expected_status_codes: Optional[
|
|
506
|
+
expected_status_codes: Optional[
|
|
507
|
+
list[int]
|
|
508
|
+
] = BaseSCIMClient.QUERY_RESPONSE_STATUS_CODES,
|
|
332
509
|
raise_scim_errors: bool = True,
|
|
333
510
|
**kwargs,
|
|
334
511
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
@@ -337,7 +514,7 @@ class SCIMClient:
|
|
|
337
514
|
- If `id` is not :data:`None`, the resource with the exact id will be reached.
|
|
338
515
|
- If `id` is :data:`None`, all the resources with the given type will be reached.
|
|
339
516
|
|
|
340
|
-
:param
|
|
517
|
+
:param resource_model: A :class:`~scim2_models.Resource` subtype or :data:`None`
|
|
341
518
|
:param id: The SCIM id of an object to get, or :data:`None`
|
|
342
519
|
:param search_request: An object detailing the search query parameters.
|
|
343
520
|
:param check_request_payload: If :data:`False`,
|
|
@@ -353,8 +530,8 @@ class SCIMClient:
|
|
|
353
530
|
|
|
354
531
|
:return:
|
|
355
532
|
- A :class:`~scim2_models.Error` object in case of error.
|
|
356
|
-
- A `
|
|
357
|
-
- A :class:`~scim2_models.ListResponse[
|
|
533
|
+
- A `resource_model` object in case of success if `id` is not :data:`None`
|
|
534
|
+
- A :class:`~scim2_models.ListResponse[resource_model]` object in case of success if `id` is :data:`None`
|
|
358
535
|
|
|
359
536
|
.. note::
|
|
360
537
|
|
|
@@ -395,71 +572,22 @@ class SCIMClient:
|
|
|
395
572
|
which value will excluded from the request payload, and which values are expected in
|
|
396
573
|
the response payload.
|
|
397
574
|
"""
|
|
398
|
-
|
|
399
|
-
self.check_resource_type(resource_type)
|
|
400
|
-
|
|
401
|
-
payload: Optional[SearchRequest]
|
|
402
|
-
if not check_request_payload:
|
|
403
|
-
payload = search_request
|
|
404
|
-
|
|
405
|
-
elif isinstance(search_request, SearchRequest):
|
|
406
|
-
payload = search_request.model_dump(
|
|
407
|
-
exclude_unset=True,
|
|
408
|
-
scim_ctx=Context.RESOURCE_QUERY_REQUEST,
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
else:
|
|
412
|
-
payload = None
|
|
413
|
-
|
|
414
|
-
url = kwargs.pop("url", self.resource_endpoint(resource_type))
|
|
415
|
-
|
|
416
|
-
if resource_type is None:
|
|
417
|
-
expected_types = [
|
|
418
|
-
*self.resource_types,
|
|
419
|
-
ListResponse[Union[self.resource_types]],
|
|
420
|
-
]
|
|
421
|
-
|
|
422
|
-
elif resource_type == ServiceProviderConfig:
|
|
423
|
-
expected_types = [resource_type]
|
|
424
|
-
if id:
|
|
425
|
-
raise SCIMClientError("ServiceProviderConfig cannot have an id")
|
|
426
|
-
|
|
427
|
-
elif id:
|
|
428
|
-
expected_types = [resource_type]
|
|
429
|
-
url = f"{url}/{id}"
|
|
430
|
-
|
|
431
|
-
else:
|
|
432
|
-
expected_types = [ListResponse[resource_type]]
|
|
433
|
-
|
|
434
|
-
try:
|
|
435
|
-
response = self.client.get(url, params=payload, **kwargs)
|
|
436
|
-
except RequestError as exc:
|
|
437
|
-
scim_exc = RequestNetworkError(source=payload)
|
|
438
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
439
|
-
scim_exc.add_note(str(exc))
|
|
440
|
-
raise scim_exc from exc
|
|
441
|
-
|
|
442
|
-
return self.check_response(
|
|
443
|
-
response=response,
|
|
444
|
-
expected_status_codes=expected_status_codes,
|
|
445
|
-
expected_types=expected_types,
|
|
446
|
-
check_response_payload=check_response_payload,
|
|
447
|
-
raise_scim_errors=raise_scim_errors,
|
|
448
|
-
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
|
|
449
|
-
)
|
|
575
|
+
raise NotImplementedError()
|
|
450
576
|
|
|
451
577
|
def search(
|
|
452
578
|
self,
|
|
453
579
|
search_request: Optional[SearchRequest] = None,
|
|
454
580
|
check_request_payload: bool = True,
|
|
455
581
|
check_response_payload: bool = True,
|
|
456
|
-
expected_status_codes: Optional[
|
|
582
|
+
expected_status_codes: Optional[
|
|
583
|
+
list[int]
|
|
584
|
+
] = BaseSCIMClient.SEARCH_RESPONSE_STATUS_CODES,
|
|
457
585
|
raise_scim_errors: bool = True,
|
|
458
586
|
**kwargs,
|
|
459
587
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
460
588
|
"""Perform a POST search request to read all available resources, as defined in :rfc:`RFC7644 §3.4.3 <7644#section-3.4.3>`.
|
|
461
589
|
|
|
462
|
-
:param
|
|
590
|
+
:param resource_models: Resource type or union of types expected
|
|
463
591
|
to be read from the response.
|
|
464
592
|
:param search_request: An object detailing the search query parameters.
|
|
465
593
|
:param check_request_payload: If :data:`False`,
|
|
@@ -476,7 +604,7 @@ class SCIMClient:
|
|
|
476
604
|
|
|
477
605
|
:return:
|
|
478
606
|
- A :class:`~scim2_models.Error` object in case of error.
|
|
479
|
-
- A :class:`~scim2_models.ListResponse[
|
|
607
|
+
- A :class:`~scim2_models.ListResponse[resource_model]` object in case of success.
|
|
480
608
|
|
|
481
609
|
:usage:
|
|
482
610
|
|
|
@@ -496,49 +624,22 @@ class SCIMClient:
|
|
|
496
624
|
which value will excluded from the request payload, and which values are expected in
|
|
497
625
|
the response payload.
|
|
498
626
|
"""
|
|
499
|
-
|
|
500
|
-
payload = search_request
|
|
501
|
-
|
|
502
|
-
else:
|
|
503
|
-
payload = (
|
|
504
|
-
search_request.model_dump(
|
|
505
|
-
exclude_unset=True, scim_ctx=Context.RESOURCE_QUERY_RESPONSE
|
|
506
|
-
)
|
|
507
|
-
if search_request
|
|
508
|
-
else None
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
url = kwargs.pop("url", "/.search")
|
|
512
|
-
|
|
513
|
-
try:
|
|
514
|
-
response = self.client.post(url, json=payload)
|
|
515
|
-
except RequestError as exc:
|
|
516
|
-
scim_exc = RequestNetworkError(source=payload)
|
|
517
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
518
|
-
scim_exc.add_note(str(exc))
|
|
519
|
-
raise scim_exc from exc
|
|
520
|
-
|
|
521
|
-
return self.check_response(
|
|
522
|
-
response=response,
|
|
523
|
-
expected_status_codes=expected_status_codes,
|
|
524
|
-
expected_types=[ListResponse[Union[self.resource_types]]],
|
|
525
|
-
check_response_payload=check_response_payload,
|
|
526
|
-
raise_scim_errors=raise_scim_errors,
|
|
527
|
-
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
|
|
528
|
-
)
|
|
627
|
+
raise NotImplementedError()
|
|
529
628
|
|
|
530
629
|
def delete(
|
|
531
630
|
self,
|
|
532
|
-
|
|
631
|
+
resource_model: type,
|
|
533
632
|
id: str,
|
|
534
633
|
check_response_payload: bool = True,
|
|
535
|
-
expected_status_codes: Optional[
|
|
634
|
+
expected_status_codes: Optional[
|
|
635
|
+
list[int]
|
|
636
|
+
] = BaseSCIMClient.DELETION_RESPONSE_STATUS_CODES,
|
|
536
637
|
raise_scim_errors: bool = True,
|
|
537
638
|
**kwargs,
|
|
538
639
|
) -> Optional[Union[Error, dict]]:
|
|
539
640
|
"""Perform a DELETE request to create, as defined in :rfc:`RFC7644 §3.6 <7644#section-3.6>`.
|
|
540
641
|
|
|
541
|
-
:param
|
|
642
|
+
:param resource_model: The type of the resource to delete.
|
|
542
643
|
:param id: The type id the resource to delete.
|
|
543
644
|
:param check_response_payload: Whether to validate that the response payload is valid.
|
|
544
645
|
If set, the raw payload will be returned.
|
|
@@ -564,31 +665,16 @@ class SCIMClient:
|
|
|
564
665
|
response = scim.delete(User, "foobar")
|
|
565
666
|
# 'response' may be None, or an Error object
|
|
566
667
|
"""
|
|
567
|
-
|
|
568
|
-
delete_url = self.resource_endpoint(resource_type) + f"/{id}"
|
|
569
|
-
url = kwargs.pop("url", delete_url)
|
|
570
|
-
|
|
571
|
-
try:
|
|
572
|
-
response = self.client.delete(url, **kwargs)
|
|
573
|
-
except RequestError as exc:
|
|
574
|
-
scim_exc = RequestNetworkError()
|
|
575
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
576
|
-
scim_exc.add_note(str(exc))
|
|
577
|
-
raise scim_exc from exc
|
|
578
|
-
|
|
579
|
-
return self.check_response(
|
|
580
|
-
response=response,
|
|
581
|
-
expected_status_codes=expected_status_codes,
|
|
582
|
-
check_response_payload=check_response_payload,
|
|
583
|
-
raise_scim_errors=raise_scim_errors,
|
|
584
|
-
)
|
|
668
|
+
raise NotImplementedError()
|
|
585
669
|
|
|
586
670
|
def replace(
|
|
587
671
|
self,
|
|
588
672
|
resource: Union[AnyResource, dict],
|
|
589
673
|
check_request_payload: bool = True,
|
|
590
674
|
check_response_payload: bool = True,
|
|
591
|
-
expected_status_codes: Optional[
|
|
675
|
+
expected_status_codes: Optional[
|
|
676
|
+
list[int]
|
|
677
|
+
] = BaseSCIMClient.REPLACEMENT_RESPONSE_STATUS_CODES,
|
|
592
678
|
raise_scim_errors: bool = True,
|
|
593
679
|
**kwargs,
|
|
594
680
|
) -> Union[AnyResource, Error, dict]:
|
|
@@ -630,58 +716,280 @@ class SCIMClient:
|
|
|
630
716
|
which value will excluded from the request payload, and which values are expected in
|
|
631
717
|
the response payload.
|
|
632
718
|
"""
|
|
633
|
-
|
|
634
|
-
payload = resource
|
|
635
|
-
url = kwargs.pop("url", None)
|
|
719
|
+
raise NotImplementedError()
|
|
636
720
|
|
|
637
|
-
else:
|
|
638
|
-
if isinstance(resource, Resource):
|
|
639
|
-
resource_type = resource.__class__
|
|
640
721
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
if not resource_type:
|
|
644
|
-
raise SCIMRequestError(
|
|
645
|
-
"Cannot guess resource type from the payload",
|
|
646
|
-
source=resource,
|
|
647
|
-
)
|
|
722
|
+
class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
723
|
+
"""Base class for asynchronous request clients."""
|
|
648
724
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
725
|
+
async def create(
|
|
726
|
+
self,
|
|
727
|
+
resource: Union[AnyResource, dict],
|
|
728
|
+
check_request_payload: bool = True,
|
|
729
|
+
check_response_payload: bool = True,
|
|
730
|
+
expected_status_codes: Optional[
|
|
731
|
+
list[int]
|
|
732
|
+
] = BaseSCIMClient.CREATION_RESPONSE_STATUS_CODES,
|
|
733
|
+
raise_scim_errors: bool = True,
|
|
734
|
+
**kwargs,
|
|
735
|
+
) -> Union[AnyResource, Error, dict]:
|
|
736
|
+
"""Perform a POST request to create, as defined in :rfc:`RFC7644 §3.3 <7644#section-3.3>`.
|
|
656
737
|
|
|
657
|
-
|
|
738
|
+
:param resource: The resource to create
|
|
739
|
+
If is a :data:`dict`, the resource type will be guessed from the schema.
|
|
740
|
+
:param check_request_payload: If :data:`False`,
|
|
741
|
+
:code:`resource` is expected to be a dict that will be passed as-is in the request.
|
|
742
|
+
:param check_response_payload: Whether to validate that the response payload is valid.
|
|
743
|
+
If set, the raw payload will be returned.
|
|
744
|
+
:param expected_status_codes: The list of expected status codes form the response.
|
|
745
|
+
If :data:`None` any status code is accepted.
|
|
746
|
+
:param raise_scim_errors: If :data:`True` and the server returned an
|
|
747
|
+
:class:`~scim2_models.Error` object, a :class:`~scim2_client.SCIMResponseErrorObject`
|
|
748
|
+
exception will be raised. If :data:`False` the error object is returned.
|
|
749
|
+
:param kwargs: Additional parameters passed to the underlying HTTP request
|
|
750
|
+
library.
|
|
658
751
|
|
|
659
|
-
|
|
660
|
-
|
|
752
|
+
:return:
|
|
753
|
+
- An :class:`~scim2_models.Error` object in case of error.
|
|
754
|
+
- The created object as returned by the server in case of success and :code:`check_response_payload` is :data:`True`.
|
|
755
|
+
- The created object payload as returned by the server in case of success and :code:`check_response_payload` is :data:`False`.
|
|
661
756
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
"url", self.resource_endpoint(resource.__class__) + f"/{resource.id}"
|
|
665
|
-
)
|
|
757
|
+
.. code-block:: python
|
|
758
|
+
:caption: Creation of a `User` resource
|
|
666
759
|
|
|
667
|
-
|
|
668
|
-
response = self.client.put(url, json=payload, **kwargs)
|
|
669
|
-
except RequestError as exc:
|
|
670
|
-
scim_network_exc = RequestNetworkError(source=payload)
|
|
671
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
672
|
-
scim_network_exc.add_note(str(exc))
|
|
673
|
-
raise scim_network_exc from exc
|
|
760
|
+
from scim2_models import User
|
|
674
761
|
|
|
675
|
-
|
|
676
|
-
response=
|
|
677
|
-
|
|
678
|
-
expected_types=([resource.__class__] if check_request_payload else None),
|
|
679
|
-
check_response_payload=check_response_payload,
|
|
680
|
-
raise_scim_errors=raise_scim_errors,
|
|
681
|
-
scim_ctx=Context.RESOURCE_REPLACEMENT_RESPONSE,
|
|
682
|
-
)
|
|
762
|
+
request = User(user_name="bjensen@example.com")
|
|
763
|
+
response = scim.create(request)
|
|
764
|
+
# 'response' may be a User or an Error object
|
|
683
765
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
766
|
+
.. tip::
|
|
767
|
+
|
|
768
|
+
Check the :attr:`~scim2_models.Context.RESOURCE_CREATION_REQUEST`
|
|
769
|
+
and :attr:`~scim2_models.Context.RESOURCE_CREATION_RESPONSE` contexts to understand
|
|
770
|
+
which value will excluded from the request payload, and which values are expected in
|
|
771
|
+
the response payload.
|
|
772
|
+
"""
|
|
773
|
+
raise NotImplementedError()
|
|
774
|
+
|
|
775
|
+
async def query(
|
|
776
|
+
self,
|
|
777
|
+
resource_model: Optional[type[Resource]] = None,
|
|
778
|
+
id: Optional[str] = None,
|
|
779
|
+
search_request: Optional[Union[SearchRequest, dict]] = None,
|
|
780
|
+
check_request_payload: bool = True,
|
|
781
|
+
check_response_payload: bool = True,
|
|
782
|
+
expected_status_codes: Optional[
|
|
783
|
+
list[int]
|
|
784
|
+
] = BaseSCIMClient.QUERY_RESPONSE_STATUS_CODES,
|
|
785
|
+
raise_scim_errors: bool = True,
|
|
786
|
+
**kwargs,
|
|
787
|
+
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
788
|
+
"""Perform a GET request to read resources, as defined in :rfc:`RFC7644 §3.4.2 <7644#section-3.4.2>`.
|
|
789
|
+
|
|
790
|
+
- If `id` is not :data:`None`, the resource with the exact id will be reached.
|
|
791
|
+
- If `id` is :data:`None`, all the resources with the given type will be reached.
|
|
792
|
+
|
|
793
|
+
:param resource_model: A :class:`~scim2_models.Resource` subtype or :data:`None`
|
|
794
|
+
:param id: The SCIM id of an object to get, or :data:`None`
|
|
795
|
+
:param search_request: An object detailing the search query parameters.
|
|
796
|
+
:param check_request_payload: If :data:`False`,
|
|
797
|
+
:code:`search_request` is expected to be a dict that will be passed as-is in the request.
|
|
798
|
+
:param check_response_payload: Whether to validate that the response payload is valid.
|
|
799
|
+
If set, the raw payload will be returned.
|
|
800
|
+
:param expected_status_codes: The list of expected status codes form the response.
|
|
801
|
+
If :data:`None` any status code is accepted.
|
|
802
|
+
:param raise_scim_errors: If :data:`True` and the server returned an
|
|
803
|
+
:class:`~scim2_models.Error` object, a :class:`~scim2_client.SCIMResponseErrorObject`
|
|
804
|
+
exception will be raised. If :data:`False` the error object is returned.
|
|
805
|
+
:param kwargs: Additional parameters passed to the underlying HTTP request library.
|
|
806
|
+
|
|
807
|
+
:return:
|
|
808
|
+
- A :class:`~scim2_models.Error` object in case of error.
|
|
809
|
+
- A `resource_model` object in case of success if `id` is not :data:`None`
|
|
810
|
+
- A :class:`~scim2_models.ListResponse[resource_model]` object in case of success if `id` is :data:`None`
|
|
811
|
+
|
|
812
|
+
.. note::
|
|
813
|
+
|
|
814
|
+
Querying a :class:`~scim2_models.ServiceProviderConfig` will return a
|
|
815
|
+
single object, and not a :class:`~scim2_models.ListResponse`.
|
|
816
|
+
|
|
817
|
+
:usage:
|
|
818
|
+
|
|
819
|
+
.. code-block:: python
|
|
820
|
+
:caption: Query of a `User` resource knowing its id
|
|
821
|
+
|
|
822
|
+
from scim2_models import User
|
|
823
|
+
|
|
824
|
+
response = scim.query(User, "my-user-id)
|
|
825
|
+
# 'response' may be a User or an Error object
|
|
826
|
+
|
|
827
|
+
.. code-block:: python
|
|
828
|
+
:caption: Query of all the `User` resources filtering the ones with `userName` starts with `john`
|
|
829
|
+
|
|
830
|
+
from scim2_models import User, SearchRequest
|
|
831
|
+
|
|
832
|
+
req = SearchRequest(filter='userName sw "john"')
|
|
833
|
+
response = scim.query(User, search_request=search_request)
|
|
834
|
+
# 'response' may be a ListResponse[User] or an Error object
|
|
835
|
+
|
|
836
|
+
.. code-block:: python
|
|
837
|
+
:caption: Query of all the available resources
|
|
838
|
+
|
|
839
|
+
from scim2_models import User, SearchRequest
|
|
840
|
+
|
|
841
|
+
response = scim.query()
|
|
842
|
+
# 'response' may be a ListResponse[Union[User, Group, ...]] or an Error object
|
|
843
|
+
|
|
844
|
+
.. tip::
|
|
845
|
+
|
|
846
|
+
Check the :attr:`~scim2_models.Context.RESOURCE_QUERY_REQUEST`
|
|
847
|
+
and :attr:`~scim2_models.Context.RESOURCE_QUERY_RESPONSE` contexts to understand
|
|
848
|
+
which value will excluded from the request payload, and which values are expected in
|
|
849
|
+
the response payload.
|
|
850
|
+
"""
|
|
851
|
+
raise NotImplementedError()
|
|
852
|
+
|
|
853
|
+
async def search(
|
|
854
|
+
self,
|
|
855
|
+
search_request: Optional[SearchRequest] = None,
|
|
856
|
+
check_request_payload: bool = True,
|
|
857
|
+
check_response_payload: bool = True,
|
|
858
|
+
expected_status_codes: Optional[
|
|
859
|
+
list[int]
|
|
860
|
+
] = BaseSCIMClient.SEARCH_RESPONSE_STATUS_CODES,
|
|
861
|
+
raise_scim_errors: bool = True,
|
|
862
|
+
**kwargs,
|
|
863
|
+
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
864
|
+
"""Perform a POST search request to read all available resources, as defined in :rfc:`RFC7644 §3.4.3 <7644#section-3.4.3>`.
|
|
865
|
+
|
|
866
|
+
:param resource_models: Resource type or union of types expected
|
|
867
|
+
to be read from the response.
|
|
868
|
+
:param search_request: An object detailing the search query parameters.
|
|
869
|
+
:param check_request_payload: If :data:`False`,
|
|
870
|
+
:code:`search_request` is expected to be a dict that will be passed as-is in the request.
|
|
871
|
+
:param check_response_payload: Whether to validate that the response payload is valid.
|
|
872
|
+
If set, the raw payload will be returned.
|
|
873
|
+
:param expected_status_codes: The list of expected status codes form the response.
|
|
874
|
+
If :data:`None` any status code is accepted.
|
|
875
|
+
:param raise_scim_errors: If :data:`True` and the server returned an
|
|
876
|
+
:class:`~scim2_models.Error` object, a :class:`~scim2_client.SCIMResponseErrorObject`
|
|
877
|
+
exception will be raised. If :data:`False` the error object is returned.
|
|
878
|
+
:param kwargs: Additional parameters passed to the underlying
|
|
879
|
+
HTTP request library.
|
|
880
|
+
|
|
881
|
+
:return:
|
|
882
|
+
- A :class:`~scim2_models.Error` object in case of error.
|
|
883
|
+
- A :class:`~scim2_models.ListResponse[resource_model]` object in case of success.
|
|
884
|
+
|
|
885
|
+
:usage:
|
|
886
|
+
|
|
887
|
+
.. code-block:: python
|
|
888
|
+
:caption: Searching for all the resources filtering the ones with `id` contains with `admin`
|
|
889
|
+
|
|
890
|
+
from scim2_models import User, SearchRequest
|
|
891
|
+
|
|
892
|
+
req = SearchRequest(filter='id co "john"')
|
|
893
|
+
response = scim.search(search_request=search_request)
|
|
894
|
+
# 'response' may be a ListResponse[User] or an Error object
|
|
895
|
+
|
|
896
|
+
.. tip::
|
|
897
|
+
|
|
898
|
+
Check the :attr:`~scim2_models.Context.SEARCH_REQUEST`
|
|
899
|
+
and :attr:`~scim2_models.Context.SEARCH_RESPONSE` contexts to understand
|
|
900
|
+
which value will excluded from the request payload, and which values are expected in
|
|
901
|
+
the response payload.
|
|
902
|
+
"""
|
|
903
|
+
raise NotImplementedError()
|
|
904
|
+
|
|
905
|
+
async def delete(
|
|
906
|
+
self,
|
|
907
|
+
resource_model: type,
|
|
908
|
+
id: str,
|
|
909
|
+
check_response_payload: bool = True,
|
|
910
|
+
expected_status_codes: Optional[
|
|
911
|
+
list[int]
|
|
912
|
+
] = BaseSCIMClient.DELETION_RESPONSE_STATUS_CODES,
|
|
913
|
+
raise_scim_errors: bool = True,
|
|
914
|
+
**kwargs,
|
|
915
|
+
) -> Optional[Union[Error, dict]]:
|
|
916
|
+
"""Perform a DELETE request to create, as defined in :rfc:`RFC7644 §3.6 <7644#section-3.6>`.
|
|
917
|
+
|
|
918
|
+
:param resource_model: The type of the resource to delete.
|
|
919
|
+
:param id: The type id the resource to delete.
|
|
920
|
+
:param check_response_payload: Whether to validate that the response payload is valid.
|
|
921
|
+
If set, the raw payload will be returned.
|
|
922
|
+
:param expected_status_codes: The list of expected status codes form the response.
|
|
923
|
+
If :data:`None` any status code is accepted.
|
|
924
|
+
:param raise_scim_errors: If :data:`True` and the server returned an
|
|
925
|
+
:class:`~scim2_models.Error` object, a :class:`~scim2_client.SCIMResponseErrorObject`
|
|
926
|
+
exception will be raised. If :data:`False` the error object is returned.
|
|
927
|
+
:param kwargs: Additional parameters passed to the underlying
|
|
928
|
+
HTTP request library.
|
|
929
|
+
|
|
930
|
+
:return:
|
|
931
|
+
- A :class:`~scim2_models.Error` object in case of error.
|
|
932
|
+
- :data:`None` in case of success.
|
|
933
|
+
|
|
934
|
+
:usage:
|
|
935
|
+
|
|
936
|
+
.. code-block:: python
|
|
937
|
+
:caption: Deleting an `User` which `id` is `foobar`
|
|
938
|
+
|
|
939
|
+
from scim2_models import User, SearchRequest
|
|
940
|
+
|
|
941
|
+
response = scim.delete(User, "foobar")
|
|
942
|
+
# 'response' may be None, or an Error object
|
|
943
|
+
"""
|
|
944
|
+
raise NotImplementedError()
|
|
945
|
+
|
|
946
|
+
async def replace(
|
|
947
|
+
self,
|
|
948
|
+
resource: Union[AnyResource, dict],
|
|
949
|
+
check_request_payload: bool = True,
|
|
950
|
+
check_response_payload: bool = True,
|
|
951
|
+
expected_status_codes: Optional[
|
|
952
|
+
list[int]
|
|
953
|
+
] = BaseSCIMClient.REPLACEMENT_RESPONSE_STATUS_CODES,
|
|
954
|
+
raise_scim_errors: bool = True,
|
|
955
|
+
**kwargs,
|
|
956
|
+
) -> Union[AnyResource, Error, dict]:
|
|
957
|
+
"""Perform a PUT request to replace a resource, as defined in :rfc:`RFC7644 §3.5.1 <7644#section-3.5.1>`.
|
|
958
|
+
|
|
959
|
+
:param resource: The new resource to replace.
|
|
960
|
+
If is a :data:`dict`, the resource type will be guessed from the schema.
|
|
961
|
+
:param check_request_payload: If :data:`False`,
|
|
962
|
+
:code:`resource` is expected to be a dict that will be passed as-is in the request.
|
|
963
|
+
:param check_response_payload: Whether to validate that the response payload is valid.
|
|
964
|
+
If set, the raw payload will be returned.
|
|
965
|
+
:param expected_status_codes: The list of expected status codes form the response.
|
|
966
|
+
If :data:`None` any status code is accepted.
|
|
967
|
+
:param raise_scim_errors: If :data:`True` and the server returned an
|
|
968
|
+
:class:`~scim2_models.Error` object, a :class:`~scim2_client.SCIMResponseErrorObject`
|
|
969
|
+
exception will be raised. If :data:`False` the error object is returned.
|
|
970
|
+
:param kwargs: Additional parameters passed to the underlying
|
|
971
|
+
HTTP request library.
|
|
972
|
+
|
|
973
|
+
:return:
|
|
974
|
+
- An :class:`~scim2_models.Error` object in case of error.
|
|
975
|
+
- The updated object as returned by the server in case of success.
|
|
976
|
+
|
|
977
|
+
:usage:
|
|
978
|
+
|
|
979
|
+
.. code-block:: python
|
|
980
|
+
:caption: Replacement of a `User` resource
|
|
981
|
+
|
|
982
|
+
from scim2_models import User
|
|
983
|
+
|
|
984
|
+
user = scim.query(User, "my-used-id")
|
|
985
|
+
user.display_name = "Fancy New Name"
|
|
986
|
+
updated_user = scim.replace(user)
|
|
987
|
+
|
|
988
|
+
.. tip::
|
|
989
|
+
|
|
990
|
+
Check the :attr:`~scim2_models.Context.RESOURCE_REPLACEMENT_REQUEST`
|
|
991
|
+
and :attr:`~scim2_models.Context.RESOURCE_REPLACEMENT_RESPONSE` contexts to understand
|
|
992
|
+
which value will excluded from the request payload, and which values are expected in
|
|
993
|
+
the response payload.
|
|
994
|
+
"""
|
|
687
995
|
raise NotImplementedError()
|