scim2-client 0.2.1__py3-none-any.whl → 0.3.0__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 +560 -250
- 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 +8 -5
- scim2_client/py.typed +0 -0
- {scim2_client-0.2.1.dist-info → scim2_client-0.3.0.dist-info}/METADATA +11 -8
- scim2_client-0.3.0.dist-info/RECORD +11 -0
- {scim2_client-0.2.1.dist-info → scim2_client-0.3.0.dist-info}/WHEEL +1 -1
- scim2_client-0.2.1.dist-info/RECORD +0 -7
- {scim2_client-0.2.1.dist-info → scim2_client-0.3.0.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,42 +134,47 @@ class SCIMClient:
|
|
|
128
134
|
:rfc:`RFC7644 §3.12 <7644#section-3.12>`.
|
|
129
135
|
"""
|
|
130
136
|
|
|
131
|
-
def __init__(self,
|
|
132
|
-
self.
|
|
133
|
-
|
|
134
|
-
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}
|
|
135
140
|
)
|
|
136
141
|
|
|
137
|
-
def
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
)
|
|
140
149
|
|
|
141
|
-
def resource_endpoint(self,
|
|
142
|
-
if
|
|
150
|
+
def resource_endpoint(self, resource_model: Optional[type[Resource]]) -> str:
|
|
151
|
+
if resource_model is None:
|
|
143
152
|
return "/"
|
|
144
153
|
|
|
145
154
|
# This one takes no final 's'
|
|
146
|
-
if
|
|
155
|
+
if resource_model is ServiceProviderConfig:
|
|
147
156
|
return "/ServiceProviderConfig"
|
|
148
157
|
|
|
149
158
|
try:
|
|
150
|
-
first_bracket_index =
|
|
151
|
-
root_name =
|
|
159
|
+
first_bracket_index = resource_model.__name__.index("[")
|
|
160
|
+
root_name = resource_model.__name__[:first_bracket_index]
|
|
152
161
|
except ValueError:
|
|
153
|
-
root_name =
|
|
162
|
+
root_name = resource_model.__name__
|
|
154
163
|
return f"/{root_name}s"
|
|
155
164
|
|
|
156
165
|
def check_response(
|
|
157
166
|
self,
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
payload: Optional[dict],
|
|
168
|
+
status_code: int,
|
|
169
|
+
headers: dict,
|
|
170
|
+
expected_status_codes: Optional[list[int]] = None,
|
|
171
|
+
expected_types: Optional[list[type[Resource]]] = None,
|
|
161
172
|
check_response_payload: bool = True,
|
|
162
173
|
raise_scim_errors: bool = True,
|
|
163
174
|
scim_ctx: Optional[Context] = None,
|
|
164
|
-
):
|
|
165
|
-
if expected_status_codes and
|
|
166
|
-
raise UnexpectedStatusCode(
|
|
175
|
+
) -> Union[Error, None, dict, type[Resource]]:
|
|
176
|
+
if expected_status_codes and status_code not in expected_status_codes:
|
|
177
|
+
raise UnexpectedStatusCode()
|
|
167
178
|
|
|
168
179
|
# Interoperability considerations: The "application/scim+json" media
|
|
169
180
|
# type is intended to identify JSON structure data that conforms to
|
|
@@ -171,24 +182,21 @@ class SCIMClient:
|
|
|
171
182
|
# SCIM are known to informally use "application/json".
|
|
172
183
|
# https://datatracker.ietf.org/doc/html/rfc7644.html#section-8.1
|
|
173
184
|
|
|
174
|
-
actual_content_type =
|
|
185
|
+
actual_content_type = headers.get("content-type", "").split(";").pop(0)
|
|
175
186
|
expected_response_content_types = ("application/scim+json", "application/json")
|
|
176
187
|
if actual_content_type not in expected_response_content_types:
|
|
177
|
-
raise UnexpectedContentType(
|
|
188
|
+
raise UnexpectedContentType(content_type=actual_content_type)
|
|
178
189
|
|
|
179
190
|
# In addition to returning an HTTP response code, implementers MUST return
|
|
180
191
|
# the errors in the body of the response in a JSON format
|
|
181
192
|
# https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.12
|
|
182
193
|
|
|
183
194
|
no_content_status_codes = [204, 205]
|
|
184
|
-
if
|
|
195
|
+
if status_code in no_content_status_codes:
|
|
185
196
|
response_payload = None
|
|
186
197
|
|
|
187
198
|
else:
|
|
188
|
-
|
|
189
|
-
response_payload = response.json()
|
|
190
|
-
except json.decoder.JSONDecodeError as exc:
|
|
191
|
-
raise UnexpectedContentFormat(source=response) from exc
|
|
199
|
+
response_payload = payload
|
|
192
200
|
|
|
193
201
|
if not check_response_payload:
|
|
194
202
|
return response_payload
|
|
@@ -209,8 +217,8 @@ class SCIMClient:
|
|
|
209
217
|
expected_types, response_payload, with_extensions=False
|
|
210
218
|
)
|
|
211
219
|
|
|
212
|
-
if not actual_type:
|
|
213
|
-
expected = ", ".join([
|
|
220
|
+
if response_payload and not actual_type:
|
|
221
|
+
expected = ", ".join([type_.__name__ for type_ in expected_types])
|
|
214
222
|
try:
|
|
215
223
|
schema = ", ".join(response_payload["schemas"])
|
|
216
224
|
message = f"Expected type {expected} but got unknown resource with schemas: {schema}"
|
|
@@ -219,22 +227,233 @@ class SCIMClient:
|
|
|
219
227
|
f"Expected type {expected} but got undefined object with no schema"
|
|
220
228
|
)
|
|
221
229
|
|
|
222
|
-
raise SCIMResponseError(message
|
|
230
|
+
raise SCIMResponseError(message)
|
|
223
231
|
|
|
224
232
|
try:
|
|
225
233
|
return actual_type.model_validate(response_payload, scim_ctx=scim_ctx)
|
|
226
234
|
except ValidationError as exc:
|
|
227
|
-
scim_exc = ResponsePayloadValidationError(
|
|
235
|
+
scim_exc = ResponsePayloadValidationError()
|
|
228
236
|
if sys.version_info >= (3, 11): # pragma: no cover
|
|
229
237
|
scim_exc.add_note(str(exc))
|
|
230
238
|
raise scim_exc from exc
|
|
231
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
|
+
|
|
232
449
|
def create(
|
|
233
450
|
self,
|
|
234
451
|
resource: Union[AnyResource, dict],
|
|
235
452
|
check_request_payload: bool = True,
|
|
236
453
|
check_response_payload: bool = True,
|
|
237
|
-
expected_status_codes: Optional[
|
|
454
|
+
expected_status_codes: Optional[
|
|
455
|
+
list[int]
|
|
456
|
+
] = BaseSCIMClient.CREATION_RESPONSE_STATUS_CODES,
|
|
238
457
|
raise_scim_errors: bool = True,
|
|
239
458
|
**kwargs,
|
|
240
459
|
) -> Union[AnyResource, Error, dict]:
|
|
@@ -275,58 +494,18 @@ class SCIMClient:
|
|
|
275
494
|
which value will excluded from the request payload, and which values are expected in
|
|
276
495
|
the response payload.
|
|
277
496
|
"""
|
|
278
|
-
|
|
279
|
-
payload = resource
|
|
280
|
-
url = kwargs.pop("url", None)
|
|
281
|
-
|
|
282
|
-
else:
|
|
283
|
-
if isinstance(resource, Resource):
|
|
284
|
-
resource_type = resource.__class__
|
|
285
|
-
|
|
286
|
-
else:
|
|
287
|
-
resource_type = Resource.get_by_payload(self.resource_types, resource)
|
|
288
|
-
if not resource_type:
|
|
289
|
-
raise SCIMRequestError(
|
|
290
|
-
"Cannot guess resource type from the payload"
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
try:
|
|
294
|
-
resource = resource_type.model_validate(resource)
|
|
295
|
-
except ValidationError as exc:
|
|
296
|
-
scim_exc = RequestPayloadValidationError(source=resource)
|
|
297
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
298
|
-
scim_exc.add_note(str(exc))
|
|
299
|
-
raise scim_exc from exc
|
|
300
|
-
|
|
301
|
-
self.check_resource_type(resource_type)
|
|
302
|
-
url = kwargs.pop("url", self.resource_endpoint(resource_type))
|
|
303
|
-
payload = resource.model_dump(scim_ctx=Context.RESOURCE_CREATION_REQUEST)
|
|
304
|
-
|
|
305
|
-
try:
|
|
306
|
-
response = self.client.post(url, json=payload, **kwargs)
|
|
307
|
-
except RequestError as exc:
|
|
308
|
-
scim_exc = RequestNetworkError(source=payload)
|
|
309
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
310
|
-
scim_exc.add_note(str(exc))
|
|
311
|
-
raise scim_exc from exc
|
|
312
|
-
|
|
313
|
-
return self.check_response(
|
|
314
|
-
response=response,
|
|
315
|
-
expected_status_codes=expected_status_codes,
|
|
316
|
-
expected_types=([resource.__class__] if check_request_payload else None),
|
|
317
|
-
check_response_payload=check_response_payload,
|
|
318
|
-
raise_scim_errors=raise_scim_errors,
|
|
319
|
-
scim_ctx=Context.RESOURCE_CREATION_RESPONSE,
|
|
320
|
-
)
|
|
497
|
+
raise NotImplementedError()
|
|
321
498
|
|
|
322
499
|
def query(
|
|
323
500
|
self,
|
|
324
|
-
|
|
501
|
+
resource_model: Optional[type[Resource]] = None,
|
|
325
502
|
id: Optional[str] = None,
|
|
326
503
|
search_request: Optional[Union[SearchRequest, dict]] = None,
|
|
327
504
|
check_request_payload: bool = True,
|
|
328
505
|
check_response_payload: bool = True,
|
|
329
|
-
expected_status_codes: Optional[
|
|
506
|
+
expected_status_codes: Optional[
|
|
507
|
+
list[int]
|
|
508
|
+
] = BaseSCIMClient.QUERY_RESPONSE_STATUS_CODES,
|
|
330
509
|
raise_scim_errors: bool = True,
|
|
331
510
|
**kwargs,
|
|
332
511
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
@@ -335,7 +514,7 @@ class SCIMClient:
|
|
|
335
514
|
- If `id` is not :data:`None`, the resource with the exact id will be reached.
|
|
336
515
|
- If `id` is :data:`None`, all the resources with the given type will be reached.
|
|
337
516
|
|
|
338
|
-
:param
|
|
517
|
+
:param resource_model: A :class:`~scim2_models.Resource` subtype or :data:`None`
|
|
339
518
|
:param id: The SCIM id of an object to get, or :data:`None`
|
|
340
519
|
:param search_request: An object detailing the search query parameters.
|
|
341
520
|
:param check_request_payload: If :data:`False`,
|
|
@@ -351,8 +530,8 @@ class SCIMClient:
|
|
|
351
530
|
|
|
352
531
|
:return:
|
|
353
532
|
- A :class:`~scim2_models.Error` object in case of error.
|
|
354
|
-
- A `
|
|
355
|
-
- 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`
|
|
356
535
|
|
|
357
536
|
.. note::
|
|
358
537
|
|
|
@@ -393,71 +572,22 @@ class SCIMClient:
|
|
|
393
572
|
which value will excluded from the request payload, and which values are expected in
|
|
394
573
|
the response payload.
|
|
395
574
|
"""
|
|
396
|
-
|
|
397
|
-
self.check_resource_type(resource_type)
|
|
398
|
-
|
|
399
|
-
if not check_request_payload:
|
|
400
|
-
payload = search_request
|
|
401
|
-
|
|
402
|
-
else:
|
|
403
|
-
payload = (
|
|
404
|
-
search_request.model_dump(
|
|
405
|
-
exclude_unset=True,
|
|
406
|
-
scim_ctx=Context.RESOURCE_QUERY_REQUEST,
|
|
407
|
-
)
|
|
408
|
-
if search_request
|
|
409
|
-
else None
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
url = kwargs.pop("url", self.resource_endpoint(resource_type))
|
|
413
|
-
|
|
414
|
-
if resource_type is None:
|
|
415
|
-
expected_types = [
|
|
416
|
-
*self.resource_types,
|
|
417
|
-
ListResponse[Union[self.resource_types]],
|
|
418
|
-
]
|
|
419
|
-
|
|
420
|
-
elif resource_type == ServiceProviderConfig:
|
|
421
|
-
expected_types = [resource_type]
|
|
422
|
-
if id:
|
|
423
|
-
raise SCIMClientError("ServiceProviderConfig cannot have an id")
|
|
424
|
-
|
|
425
|
-
elif id:
|
|
426
|
-
expected_types = [resource_type]
|
|
427
|
-
url = f"{url}/{id}"
|
|
428
|
-
|
|
429
|
-
else:
|
|
430
|
-
expected_types = [ListResponse[resource_type]]
|
|
431
|
-
|
|
432
|
-
try:
|
|
433
|
-
response = self.client.get(url, params=payload, **kwargs)
|
|
434
|
-
except RequestError as exc:
|
|
435
|
-
scim_exc = RequestNetworkError(source=payload)
|
|
436
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
437
|
-
scim_exc.add_note(str(exc))
|
|
438
|
-
raise scim_exc from exc
|
|
439
|
-
|
|
440
|
-
return self.check_response(
|
|
441
|
-
response=response,
|
|
442
|
-
expected_status_codes=expected_status_codes,
|
|
443
|
-
expected_types=expected_types,
|
|
444
|
-
check_response_payload=check_response_payload,
|
|
445
|
-
raise_scim_errors=raise_scim_errors,
|
|
446
|
-
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
|
|
447
|
-
)
|
|
575
|
+
raise NotImplementedError()
|
|
448
576
|
|
|
449
577
|
def search(
|
|
450
578
|
self,
|
|
451
579
|
search_request: Optional[SearchRequest] = None,
|
|
452
580
|
check_request_payload: bool = True,
|
|
453
581
|
check_response_payload: bool = True,
|
|
454
|
-
expected_status_codes: Optional[
|
|
582
|
+
expected_status_codes: Optional[
|
|
583
|
+
list[int]
|
|
584
|
+
] = BaseSCIMClient.SEARCH_RESPONSE_STATUS_CODES,
|
|
455
585
|
raise_scim_errors: bool = True,
|
|
456
586
|
**kwargs,
|
|
457
587
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
458
588
|
"""Perform a POST search request to read all available resources, as defined in :rfc:`RFC7644 §3.4.3 <7644#section-3.4.3>`.
|
|
459
589
|
|
|
460
|
-
:param
|
|
590
|
+
:param resource_models: Resource type or union of types expected
|
|
461
591
|
to be read from the response.
|
|
462
592
|
:param search_request: An object detailing the search query parameters.
|
|
463
593
|
:param check_request_payload: If :data:`False`,
|
|
@@ -474,7 +604,7 @@ class SCIMClient:
|
|
|
474
604
|
|
|
475
605
|
:return:
|
|
476
606
|
- A :class:`~scim2_models.Error` object in case of error.
|
|
477
|
-
- A :class:`~scim2_models.ListResponse[
|
|
607
|
+
- A :class:`~scim2_models.ListResponse[resource_model]` object in case of success.
|
|
478
608
|
|
|
479
609
|
:usage:
|
|
480
610
|
|
|
@@ -494,49 +624,22 @@ class SCIMClient:
|
|
|
494
624
|
which value will excluded from the request payload, and which values are expected in
|
|
495
625
|
the response payload.
|
|
496
626
|
"""
|
|
497
|
-
|
|
498
|
-
payload = search_request
|
|
499
|
-
|
|
500
|
-
else:
|
|
501
|
-
payload = (
|
|
502
|
-
search_request.model_dump(
|
|
503
|
-
exclude_unset=True, scim_ctx=Context.RESOURCE_QUERY_RESPONSE
|
|
504
|
-
)
|
|
505
|
-
if search_request
|
|
506
|
-
else None
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
url = kwargs.pop("url", "/.search")
|
|
510
|
-
|
|
511
|
-
try:
|
|
512
|
-
response = self.client.post(url, json=payload)
|
|
513
|
-
except RequestError as exc:
|
|
514
|
-
scim_exc = RequestNetworkError(source=payload)
|
|
515
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
516
|
-
scim_exc.add_note(str(exc))
|
|
517
|
-
raise scim_exc from exc
|
|
518
|
-
|
|
519
|
-
return self.check_response(
|
|
520
|
-
response=response,
|
|
521
|
-
expected_status_codes=expected_status_codes,
|
|
522
|
-
expected_types=[ListResponse[Union[self.resource_types]]],
|
|
523
|
-
check_response_payload=check_response_payload,
|
|
524
|
-
raise_scim_errors=raise_scim_errors,
|
|
525
|
-
scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
|
|
526
|
-
)
|
|
627
|
+
raise NotImplementedError()
|
|
527
628
|
|
|
528
629
|
def delete(
|
|
529
630
|
self,
|
|
530
|
-
|
|
631
|
+
resource_model: type,
|
|
531
632
|
id: str,
|
|
532
633
|
check_response_payload: bool = True,
|
|
533
|
-
expected_status_codes: Optional[
|
|
634
|
+
expected_status_codes: Optional[
|
|
635
|
+
list[int]
|
|
636
|
+
] = BaseSCIMClient.DELETION_RESPONSE_STATUS_CODES,
|
|
534
637
|
raise_scim_errors: bool = True,
|
|
535
638
|
**kwargs,
|
|
536
639
|
) -> Optional[Union[Error, dict]]:
|
|
537
640
|
"""Perform a DELETE request to create, as defined in :rfc:`RFC7644 §3.6 <7644#section-3.6>`.
|
|
538
641
|
|
|
539
|
-
:param
|
|
642
|
+
:param resource_model: The type of the resource to delete.
|
|
540
643
|
:param id: The type id the resource to delete.
|
|
541
644
|
:param check_response_payload: Whether to validate that the response payload is valid.
|
|
542
645
|
If set, the raw payload will be returned.
|
|
@@ -562,31 +665,16 @@ class SCIMClient:
|
|
|
562
665
|
response = scim.delete(User, "foobar")
|
|
563
666
|
# 'response' may be None, or an Error object
|
|
564
667
|
"""
|
|
565
|
-
|
|
566
|
-
delete_url = self.resource_endpoint(resource_type) + f"/{id}"
|
|
567
|
-
url = kwargs.pop("url", delete_url)
|
|
568
|
-
|
|
569
|
-
try:
|
|
570
|
-
response = self.client.delete(url, **kwargs)
|
|
571
|
-
except RequestError as exc:
|
|
572
|
-
scim_exc = RequestNetworkError()
|
|
573
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
574
|
-
scim_exc.add_note(str(exc))
|
|
575
|
-
raise scim_exc from exc
|
|
576
|
-
|
|
577
|
-
return self.check_response(
|
|
578
|
-
response=response,
|
|
579
|
-
expected_status_codes=expected_status_codes,
|
|
580
|
-
check_response_payload=check_response_payload,
|
|
581
|
-
raise_scim_errors=raise_scim_errors,
|
|
582
|
-
)
|
|
668
|
+
raise NotImplementedError()
|
|
583
669
|
|
|
584
670
|
def replace(
|
|
585
671
|
self,
|
|
586
672
|
resource: Union[AnyResource, dict],
|
|
587
673
|
check_request_payload: bool = True,
|
|
588
674
|
check_response_payload: bool = True,
|
|
589
|
-
expected_status_codes: Optional[
|
|
675
|
+
expected_status_codes: Optional[
|
|
676
|
+
list[int]
|
|
677
|
+
] = BaseSCIMClient.REPLACEMENT_RESPONSE_STATUS_CODES,
|
|
590
678
|
raise_scim_errors: bool = True,
|
|
591
679
|
**kwargs,
|
|
592
680
|
) -> Union[AnyResource, Error, dict]:
|
|
@@ -628,58 +716,280 @@ class SCIMClient:
|
|
|
628
716
|
which value will excluded from the request payload, and which values are expected in
|
|
629
717
|
the response payload.
|
|
630
718
|
"""
|
|
631
|
-
|
|
632
|
-
payload = resource
|
|
633
|
-
url = kwargs.pop("url", None)
|
|
719
|
+
raise NotImplementedError()
|
|
634
720
|
|
|
635
|
-
else:
|
|
636
|
-
if isinstance(resource, Resource):
|
|
637
|
-
resource_type = resource.__class__
|
|
638
721
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if not resource_type:
|
|
642
|
-
raise SCIMRequestError(
|
|
643
|
-
"Cannot guess resource type from the payload",
|
|
644
|
-
source=resource,
|
|
645
|
-
)
|
|
722
|
+
class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
723
|
+
"""Base class for asynchronous request clients."""
|
|
646
724
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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>`.
|
|
737
|
+
|
|
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.
|
|
654
751
|
|
|
655
|
-
|
|
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`.
|
|
656
756
|
|
|
657
|
-
|
|
658
|
-
|
|
757
|
+
.. code-block:: python
|
|
758
|
+
:caption: Creation of a `User` resource
|
|
659
759
|
|
|
660
|
-
|
|
661
|
-
url = kwargs.pop(
|
|
662
|
-
"url", self.resource_endpoint(resource.__class__) + f"/{resource.id}"
|
|
663
|
-
)
|
|
760
|
+
from scim2_models import User
|
|
664
761
|
|
|
665
|
-
|
|
666
|
-
response =
|
|
667
|
-
|
|
668
|
-
scim_exc = RequestNetworkError(source=payload)
|
|
669
|
-
if sys.version_info >= (3, 11): # pragma: no cover
|
|
670
|
-
scim_exc.add_note(str(exc))
|
|
671
|
-
raise scim_exc from exc
|
|
762
|
+
request = User(user_name="bjensen@example.com")
|
|
763
|
+
response = scim.create(request)
|
|
764
|
+
# 'response' may be a User or an Error object
|
|
672
765
|
|
|
673
|
-
|
|
674
|
-
response=response,
|
|
675
|
-
expected_status_codes=expected_status_codes,
|
|
676
|
-
expected_types=([resource.__class__] if check_request_payload else None),
|
|
677
|
-
check_response_payload=check_response_payload,
|
|
678
|
-
raise_scim_errors=raise_scim_errors,
|
|
679
|
-
scim_ctx=Context.RESOURCE_REPLACEMENT_RESPONSE,
|
|
680
|
-
)
|
|
766
|
+
.. tip::
|
|
681
767
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
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
|
+
"""
|
|
685
995
|
raise NotImplementedError()
|