scim2-client 0.5.2__py3-none-any.whl → 0.6.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/client.py +229 -26
- scim2_client/engines/httpx.py +95 -23
- scim2_client/engines/werkzeug.py +60 -18
- scim2_client/errors.py +11 -9
- {scim2_client-0.5.2.dist-info → scim2_client-0.6.1.dist-info}/METADATA +2 -2
- scim2_client-0.6.1.dist-info/RECORD +11 -0
- scim2_client-0.5.2.dist-info/RECORD +0 -11
- {scim2_client-0.5.2.dist-info → scim2_client-0.6.1.dist-info}/WHEEL +0 -0
- {scim2_client-0.5.2.dist-info → scim2_client-0.6.1.dist-info}/licenses/LICENSE.md +0 -0
scim2_client/client.py
CHANGED
|
@@ -3,6 +3,7 @@ import sys
|
|
|
3
3
|
from collections.abc import Collection
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from typing import Optional
|
|
6
|
+
from typing import TypeVar
|
|
6
7
|
from typing import Union
|
|
7
8
|
|
|
8
9
|
from pydantic import ValidationError
|
|
@@ -27,6 +28,8 @@ from scim2_client.errors import SCIMResponseErrorObject
|
|
|
27
28
|
from scim2_client.errors import UnexpectedContentType
|
|
28
29
|
from scim2_client.errors import UnexpectedStatusCode
|
|
29
30
|
|
|
31
|
+
ResourceT = TypeVar("ResourceT", bound=Resource)
|
|
32
|
+
|
|
30
33
|
BASE_HEADERS = {
|
|
31
34
|
"Accept": "application/scim+json",
|
|
32
35
|
"Content-Type": "application/scim+json",
|
|
@@ -151,6 +154,26 @@ class SCIMClient:
|
|
|
151
154
|
:rfc:`RFC7644 §3.12 <7644#section-3.12>`.
|
|
152
155
|
"""
|
|
153
156
|
|
|
157
|
+
PATCH_RESPONSE_STATUS_CODES: list[int] = [
|
|
158
|
+
200,
|
|
159
|
+
204,
|
|
160
|
+
307,
|
|
161
|
+
308,
|
|
162
|
+
400,
|
|
163
|
+
401,
|
|
164
|
+
403,
|
|
165
|
+
404,
|
|
166
|
+
409,
|
|
167
|
+
412,
|
|
168
|
+
500,
|
|
169
|
+
501,
|
|
170
|
+
]
|
|
171
|
+
"""Resource patching HTTP codes.
|
|
172
|
+
|
|
173
|
+
As defined at :rfc:`RFC7644 §3.5.2 <7644#section-3.5.2>` and
|
|
174
|
+
:rfc:`RFC7644 §3.12 <7644#section-3.12>`.
|
|
175
|
+
"""
|
|
176
|
+
|
|
154
177
|
def __init__(
|
|
155
178
|
self,
|
|
156
179
|
resource_models: Optional[Collection[type[Resource]]] = None,
|
|
@@ -179,13 +202,16 @@ class SCIMClient:
|
|
|
179
202
|
return resource_model
|
|
180
203
|
return None
|
|
181
204
|
|
|
182
|
-
def
|
|
205
|
+
def _check_resource_model(
|
|
183
206
|
self, resource_model: type[Resource], payload=None
|
|
184
207
|
) -> None:
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
208
|
+
schema_to_check = resource_model.model_fields["schemas"].default[0]
|
|
209
|
+
for element in self.resource_models:
|
|
210
|
+
schema = element.model_fields["schemas"].default[0]
|
|
211
|
+
if schema_to_check == schema:
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
if resource_model not in CONFIG_RESOURCES:
|
|
189
215
|
raise SCIMRequestError(
|
|
190
216
|
f"Unknown resource type: '{resource_model}'", source=payload
|
|
191
217
|
)
|
|
@@ -299,11 +325,15 @@ class SCIMClient:
|
|
|
299
325
|
if not expected_types:
|
|
300
326
|
return response_payload
|
|
301
327
|
|
|
328
|
+
# For no-content responses, return None directly
|
|
329
|
+
if response_payload is None:
|
|
330
|
+
return None
|
|
331
|
+
|
|
302
332
|
actual_type = Resource.get_by_payload(
|
|
303
333
|
expected_types, response_payload, with_extensions=False
|
|
304
334
|
)
|
|
305
335
|
|
|
306
|
-
if
|
|
336
|
+
if not actual_type:
|
|
307
337
|
expected = ", ".join([type_.__name__ for type_ in expected_types])
|
|
308
338
|
try:
|
|
309
339
|
schema = ", ".join(response_payload["schemas"])
|
|
@@ -323,12 +353,11 @@ class SCIMClient:
|
|
|
323
353
|
scim_exc.add_note(str(exc))
|
|
324
354
|
raise scim_exc from exc
|
|
325
355
|
|
|
326
|
-
def
|
|
356
|
+
def _prepare_create_request(
|
|
327
357
|
self,
|
|
328
358
|
resource: Union[AnyResource, dict],
|
|
329
359
|
check_request_payload: Optional[bool] = None,
|
|
330
360
|
expected_status_codes: Optional[list[int]] = None,
|
|
331
|
-
raise_scim_errors: Optional[bool] = None,
|
|
332
361
|
**kwargs,
|
|
333
362
|
) -> RequestPayload:
|
|
334
363
|
req = RequestPayload(
|
|
@@ -361,7 +390,7 @@ class SCIMClient:
|
|
|
361
390
|
scim_validation_exc.add_note(str(exc))
|
|
362
391
|
raise scim_validation_exc from exc
|
|
363
392
|
|
|
364
|
-
self.
|
|
393
|
+
self._check_resource_model(resource_model, resource)
|
|
365
394
|
req.expected_types = [resource.__class__]
|
|
366
395
|
req.url = req.request_kwargs.pop(
|
|
367
396
|
"url", self.resource_endpoint(resource_model)
|
|
@@ -372,14 +401,13 @@ class SCIMClient:
|
|
|
372
401
|
|
|
373
402
|
return req
|
|
374
403
|
|
|
375
|
-
def
|
|
404
|
+
def _prepare_query_request(
|
|
376
405
|
self,
|
|
377
406
|
resource_model: Optional[type[Resource]] = None,
|
|
378
407
|
id: Optional[str] = None,
|
|
379
408
|
search_request: Optional[Union[SearchRequest, dict]] = None,
|
|
380
409
|
check_request_payload: Optional[bool] = None,
|
|
381
410
|
expected_status_codes: Optional[list[int]] = None,
|
|
382
|
-
raise_scim_errors: Optional[bool] = None,
|
|
383
411
|
**kwargs,
|
|
384
412
|
) -> RequestPayload:
|
|
385
413
|
req = RequestPayload(
|
|
@@ -391,7 +419,7 @@ class SCIMClient:
|
|
|
391
419
|
check_request_payload = self.check_request_payload
|
|
392
420
|
|
|
393
421
|
if resource_model and check_request_payload:
|
|
394
|
-
self.
|
|
422
|
+
self._check_resource_model(resource_model)
|
|
395
423
|
|
|
396
424
|
payload: Optional[SearchRequest]
|
|
397
425
|
if not check_request_payload:
|
|
@@ -429,12 +457,11 @@ class SCIMClient:
|
|
|
429
457
|
|
|
430
458
|
return req
|
|
431
459
|
|
|
432
|
-
def
|
|
460
|
+
def _prepare_search_request(
|
|
433
461
|
self,
|
|
434
462
|
search_request: Optional[SearchRequest] = None,
|
|
435
463
|
check_request_payload: Optional[bool] = None,
|
|
436
464
|
expected_status_codes: Optional[list[int]] = None,
|
|
437
|
-
raise_scim_errors: Optional[bool] = None,
|
|
438
465
|
**kwargs,
|
|
439
466
|
) -> RequestPayload:
|
|
440
467
|
req = RequestPayload(
|
|
@@ -461,12 +488,11 @@ class SCIMClient:
|
|
|
461
488
|
req.expected_types = [ListResponse[Union[self.resource_models]]]
|
|
462
489
|
return req
|
|
463
490
|
|
|
464
|
-
def
|
|
491
|
+
def _prepare_delete_request(
|
|
465
492
|
self,
|
|
466
493
|
resource_model: type,
|
|
467
494
|
id: str,
|
|
468
495
|
expected_status_codes: Optional[list[int]] = None,
|
|
469
|
-
raise_scim_errors: Optional[bool] = None,
|
|
470
496
|
**kwargs,
|
|
471
497
|
) -> RequestPayload:
|
|
472
498
|
req = RequestPayload(
|
|
@@ -474,17 +500,16 @@ class SCIMClient:
|
|
|
474
500
|
request_kwargs=kwargs,
|
|
475
501
|
)
|
|
476
502
|
|
|
477
|
-
self.
|
|
503
|
+
self._check_resource_model(resource_model)
|
|
478
504
|
delete_url = self.resource_endpoint(resource_model) + f"/{id}"
|
|
479
505
|
req.url = req.request_kwargs.pop("url", delete_url)
|
|
480
506
|
return req
|
|
481
507
|
|
|
482
|
-
def
|
|
508
|
+
def _prepare_replace_request(
|
|
483
509
|
self,
|
|
484
510
|
resource: Union[AnyResource, dict],
|
|
485
511
|
check_request_payload: Optional[bool] = None,
|
|
486
512
|
expected_status_codes: Optional[list[int]] = None,
|
|
487
|
-
raise_scim_errors: Optional[bool] = None,
|
|
488
513
|
**kwargs,
|
|
489
514
|
) -> RequestPayload:
|
|
490
515
|
req = RequestPayload(
|
|
@@ -519,7 +544,7 @@ class SCIMClient:
|
|
|
519
544
|
scim_validation_exc.add_note(str(exc))
|
|
520
545
|
raise scim_validation_exc from exc
|
|
521
546
|
|
|
522
|
-
self.
|
|
547
|
+
self._check_resource_model(resource_model, resource)
|
|
523
548
|
|
|
524
549
|
if not resource.id:
|
|
525
550
|
raise SCIMRequestError("Resource must have an id", source=resource)
|
|
@@ -534,9 +559,75 @@ class SCIMClient:
|
|
|
534
559
|
|
|
535
560
|
return req
|
|
536
561
|
|
|
562
|
+
def _prepare_patch_request(
|
|
563
|
+
self,
|
|
564
|
+
resource_model: type[ResourceT],
|
|
565
|
+
id: str,
|
|
566
|
+
patch_op: Union[PatchOp[ResourceT], dict],
|
|
567
|
+
check_request_payload: Optional[bool] = None,
|
|
568
|
+
expected_status_codes: Optional[list[int]] = None,
|
|
569
|
+
**kwargs,
|
|
570
|
+
) -> RequestPayload:
|
|
571
|
+
"""Prepare a PATCH request payload.
|
|
572
|
+
|
|
573
|
+
:param resource_model: The resource type to modify (e.g., User, Group).
|
|
574
|
+
:param id: The resource ID.
|
|
575
|
+
:param patch_op: A PatchOp instance parameterized with the same resource type as resource_model
|
|
576
|
+
(e.g., PatchOp[User] when resource_model is User), or a dict representation.
|
|
577
|
+
:param check_request_payload: If :data:`False`, :code:`patch_op` is expected to be a dict
|
|
578
|
+
that will be passed as-is in the request. This value can be
|
|
579
|
+
overwritten in methods.
|
|
580
|
+
:param expected_status_codes: List of HTTP status codes expected for this request.
|
|
581
|
+
:param raise_scim_errors: If :data:`True` and the server returned an
|
|
582
|
+
:class:`~scim2_models.Error` object during a request, a
|
|
583
|
+
:class:`~scim2_client.SCIMResponseErrorObject` exception will be raised.
|
|
584
|
+
:param kwargs: Additional request parameters.
|
|
585
|
+
:return: The prepared request payload.
|
|
586
|
+
"""
|
|
587
|
+
req = RequestPayload(
|
|
588
|
+
expected_status_codes=expected_status_codes,
|
|
589
|
+
request_kwargs=kwargs,
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
if check_request_payload is None:
|
|
593
|
+
check_request_payload = self.check_request_payload
|
|
594
|
+
|
|
595
|
+
self._check_resource_model(resource_model)
|
|
596
|
+
|
|
597
|
+
if not check_request_payload:
|
|
598
|
+
req.payload = patch_op
|
|
599
|
+
req.url = req.request_kwargs.pop(
|
|
600
|
+
"url", f"{self.resource_endpoint(resource_model)}/{id}"
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
else:
|
|
604
|
+
if isinstance(patch_op, dict):
|
|
605
|
+
req.payload = patch_op
|
|
606
|
+
else:
|
|
607
|
+
try:
|
|
608
|
+
req.payload = patch_op.model_dump(
|
|
609
|
+
scim_ctx=Context.RESOURCE_PATCH_REQUEST
|
|
610
|
+
)
|
|
611
|
+
except ValidationError as exc:
|
|
612
|
+
scim_validation_exc = RequestPayloadValidationError(source=patch_op)
|
|
613
|
+
if sys.version_info >= (3, 11): # pragma: no cover
|
|
614
|
+
scim_validation_exc.add_note(str(exc))
|
|
615
|
+
raise scim_validation_exc from exc
|
|
616
|
+
|
|
617
|
+
req.url = req.request_kwargs.pop(
|
|
618
|
+
"url", f"{self.resource_endpoint(resource_model)}/{id}"
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
req.expected_types = [resource_model]
|
|
622
|
+
return req
|
|
623
|
+
|
|
537
624
|
def modify(
|
|
538
|
-
self,
|
|
539
|
-
|
|
625
|
+
self,
|
|
626
|
+
resource_model: type[ResourceT],
|
|
627
|
+
id: str,
|
|
628
|
+
patch_op: Union[PatchOp[ResourceT], dict],
|
|
629
|
+
**kwargs,
|
|
630
|
+
) -> Optional[Union[ResourceT, Error, dict]]:
|
|
540
631
|
raise NotImplementedError()
|
|
541
632
|
|
|
542
633
|
def build_resource_models(
|
|
@@ -552,13 +643,13 @@ class SCIMClient:
|
|
|
552
643
|
for schema, resource_type in resource_types_by_schema.items():
|
|
553
644
|
schema_obj = schema_objs_by_schema[schema]
|
|
554
645
|
model = Resource.from_schema(schema_obj)
|
|
555
|
-
extensions =
|
|
646
|
+
extensions: tuple[type[Extension], ...] = ()
|
|
556
647
|
for ext_schema in resource_type.schema_extensions or []:
|
|
557
648
|
schema_obj = schema_objs_by_schema[ext_schema.schema_]
|
|
558
649
|
extension = Extension.from_schema(schema_obj)
|
|
559
|
-
extensions
|
|
650
|
+
extensions = extensions + (extension,)
|
|
560
651
|
if extensions:
|
|
561
|
-
model = model[Union[
|
|
652
|
+
model = model[Union[extensions]]
|
|
562
653
|
resource_models.append(model)
|
|
563
654
|
|
|
564
655
|
return tuple(resource_models)
|
|
@@ -615,7 +706,7 @@ class BaseSyncSCIMClient(SCIMClient):
|
|
|
615
706
|
|
|
616
707
|
def query(
|
|
617
708
|
self,
|
|
618
|
-
resource_model: Optional[type[
|
|
709
|
+
resource_model: Optional[type[AnyResource]] = None,
|
|
619
710
|
id: Optional[str] = None,
|
|
620
711
|
search_request: Optional[Union[SearchRequest, dict]] = None,
|
|
621
712
|
check_request_payload: Optional[bool] = None,
|
|
@@ -820,6 +911,62 @@ class BaseSyncSCIMClient(SCIMClient):
|
|
|
820
911
|
"""
|
|
821
912
|
raise NotImplementedError()
|
|
822
913
|
|
|
914
|
+
def modify(
|
|
915
|
+
self,
|
|
916
|
+
resource_model: type[ResourceT],
|
|
917
|
+
id: str,
|
|
918
|
+
patch_op: Union[PatchOp[ResourceT], dict],
|
|
919
|
+
check_request_payload: Optional[bool] = None,
|
|
920
|
+
check_response_payload: Optional[bool] = None,
|
|
921
|
+
expected_status_codes: Optional[
|
|
922
|
+
list[int]
|
|
923
|
+
] = SCIMClient.PATCH_RESPONSE_STATUS_CODES,
|
|
924
|
+
raise_scim_errors: Optional[bool] = None,
|
|
925
|
+
**kwargs,
|
|
926
|
+
) -> Optional[Union[ResourceT, Error, dict]]:
|
|
927
|
+
"""Perform a PATCH request to modify a resource, as defined in :rfc:`RFC7644 §3.5.2 <7644#section-3.5.2>`.
|
|
928
|
+
|
|
929
|
+
:param resource_model: The type of the resource to modify.
|
|
930
|
+
:param id: The id of the resource to modify.
|
|
931
|
+
:param patch_op: The :class:`~scim2_models.PatchOp` object describing the modifications.
|
|
932
|
+
Must be parameterized with the same resource type as ``resource_model``
|
|
933
|
+
(e.g., :code:`PatchOp[User]` when ``resource_model`` is :code:`User`).
|
|
934
|
+
:param check_request_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_request_payload`.
|
|
935
|
+
:param check_response_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_response_payload`.
|
|
936
|
+
:param expected_status_codes: The list of expected status codes form the response.
|
|
937
|
+
If :data:`None` any status code is accepted.
|
|
938
|
+
:param raise_scim_errors: If set, overwrites :paramref:`scim2_client.SCIMClient.raise_scim_errors`.
|
|
939
|
+
:param kwargs: Additional parameters passed to the underlying
|
|
940
|
+
HTTP request library.
|
|
941
|
+
|
|
942
|
+
:return:
|
|
943
|
+
- An :class:`~scim2_models.Error` object in case of error.
|
|
944
|
+
- The updated object as returned by the server in case of success if status code is 200.
|
|
945
|
+
- :data:`None` in case of success if status code is 204.
|
|
946
|
+
|
|
947
|
+
:usage:
|
|
948
|
+
|
|
949
|
+
.. code-block:: python
|
|
950
|
+
:caption: Modification of a `User` resource
|
|
951
|
+
|
|
952
|
+
from scim2_models import User, PatchOp, PatchOperation
|
|
953
|
+
|
|
954
|
+
operation = PatchOperation(
|
|
955
|
+
op="replace", path="displayName", value="New Display Name"
|
|
956
|
+
)
|
|
957
|
+
patch_op = PatchOp[User](operations=[operation])
|
|
958
|
+
response = scim.modify(User, "my-user-id", patch_op)
|
|
959
|
+
# 'response' may be a User, None, or an Error object
|
|
960
|
+
|
|
961
|
+
.. tip::
|
|
962
|
+
|
|
963
|
+
Check the :attr:`~scim2_models.Context.RESOURCE_PATCH_REQUEST`
|
|
964
|
+
and :attr:`~scim2_models.Context.RESOURCE_PATCH_RESPONSE` contexts to understand
|
|
965
|
+
which value will excluded from the request payload, and which values are expected in
|
|
966
|
+
the response payload.
|
|
967
|
+
"""
|
|
968
|
+
raise NotImplementedError()
|
|
969
|
+
|
|
823
970
|
def discover(self, schemas=True, resource_types=True, service_provider_config=True):
|
|
824
971
|
"""Dynamically discover the server configuration objects.
|
|
825
972
|
|
|
@@ -1097,6 +1244,62 @@ class BaseAsyncSCIMClient(SCIMClient):
|
|
|
1097
1244
|
"""
|
|
1098
1245
|
raise NotImplementedError()
|
|
1099
1246
|
|
|
1247
|
+
async def modify(
|
|
1248
|
+
self,
|
|
1249
|
+
resource_model: type[ResourceT],
|
|
1250
|
+
id: str,
|
|
1251
|
+
patch_op: Union[PatchOp[ResourceT], dict],
|
|
1252
|
+
check_request_payload: Optional[bool] = None,
|
|
1253
|
+
check_response_payload: Optional[bool] = None,
|
|
1254
|
+
expected_status_codes: Optional[
|
|
1255
|
+
list[int]
|
|
1256
|
+
] = SCIMClient.PATCH_RESPONSE_STATUS_CODES,
|
|
1257
|
+
raise_scim_errors: Optional[bool] = None,
|
|
1258
|
+
**kwargs,
|
|
1259
|
+
) -> Optional[Union[ResourceT, Error, dict]]:
|
|
1260
|
+
"""Perform a PATCH request to modify a resource, as defined in :rfc:`RFC7644 §3.5.2 <7644#section-3.5.2>`.
|
|
1261
|
+
|
|
1262
|
+
:param resource_model: The type of the resource to modify.
|
|
1263
|
+
:param id: The id of the resource to modify.
|
|
1264
|
+
:param patch_op: The :class:`~scim2_models.PatchOp` object describing the modifications.
|
|
1265
|
+
Must be parameterized with the same resource type as ``resource_model``
|
|
1266
|
+
(e.g., :code:`PatchOp[User]` when ``resource_model`` is :code:`User`).
|
|
1267
|
+
:param check_request_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_request_payload`.
|
|
1268
|
+
:param check_response_payload: If set, overwrites :paramref:`scim2_client.SCIMClient.check_response_payload`.
|
|
1269
|
+
:param expected_status_codes: The list of expected status codes form the response.
|
|
1270
|
+
If :data:`None` any status code is accepted.
|
|
1271
|
+
:param raise_scim_errors: If set, overwrites :paramref:`scim2_client.SCIMClient.raise_scim_errors`.
|
|
1272
|
+
:param kwargs: Additional parameters passed to the underlying
|
|
1273
|
+
HTTP request library.
|
|
1274
|
+
|
|
1275
|
+
:return:
|
|
1276
|
+
- An :class:`~scim2_models.Error` object in case of error.
|
|
1277
|
+
- The updated object as returned by the server in case of success if status code is 200.
|
|
1278
|
+
- :data:`None` in case of success if status code is 204.
|
|
1279
|
+
|
|
1280
|
+
:usage:
|
|
1281
|
+
|
|
1282
|
+
.. code-block:: python
|
|
1283
|
+
:caption: Modification of a `User` resource
|
|
1284
|
+
|
|
1285
|
+
from scim2_models import User, PatchOp, PatchOperation
|
|
1286
|
+
|
|
1287
|
+
operation = PatchOperation(
|
|
1288
|
+
op="replace", path="displayName", value="New Display Name"
|
|
1289
|
+
)
|
|
1290
|
+
patch_op = PatchOp[User](operations=[operation])
|
|
1291
|
+
response = await scim.modify(User, "my-user-id", patch_op)
|
|
1292
|
+
# 'response' may be a User, None, or an Error object
|
|
1293
|
+
|
|
1294
|
+
.. tip::
|
|
1295
|
+
|
|
1296
|
+
Check the :attr:`~scim2_models.Context.RESOURCE_PATCH_REQUEST`
|
|
1297
|
+
and :attr:`~scim2_models.Context.RESOURCE_PATCH_RESPONSE` contexts to understand
|
|
1298
|
+
which value will excluded from the request payload, and which values are expected in
|
|
1299
|
+
the response payload.
|
|
1300
|
+
"""
|
|
1301
|
+
raise NotImplementedError()
|
|
1302
|
+
|
|
1100
1303
|
async def discover(
|
|
1101
1304
|
self, schemas=True, resource_types=True, service_provider_config=True
|
|
1102
1305
|
):
|
scim2_client/engines/httpx.py
CHANGED
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
import sys
|
|
3
3
|
from contextlib import contextmanager
|
|
4
4
|
from typing import Optional
|
|
5
|
+
from typing import TypeVar
|
|
5
6
|
from typing import Union
|
|
6
7
|
|
|
7
8
|
from httpx import Client
|
|
@@ -11,6 +12,7 @@ from scim2_models import AnyResource
|
|
|
11
12
|
from scim2_models import Context
|
|
12
13
|
from scim2_models import Error
|
|
13
14
|
from scim2_models import ListResponse
|
|
15
|
+
from scim2_models import PatchOp
|
|
14
16
|
from scim2_models import Resource
|
|
15
17
|
from scim2_models import SearchRequest
|
|
16
18
|
|
|
@@ -20,6 +22,8 @@ from scim2_client.errors import RequestNetworkError
|
|
|
20
22
|
from scim2_client.errors import SCIMClientError
|
|
21
23
|
from scim2_client.errors import UnexpectedContentFormat
|
|
22
24
|
|
|
25
|
+
ResourceT = TypeVar("ResourceT", bound=Resource)
|
|
26
|
+
|
|
23
27
|
|
|
24
28
|
@contextmanager
|
|
25
29
|
def handle_request_error(payload=None):
|
|
@@ -77,11 +81,10 @@ class SyncSCIMClient(BaseSyncSCIMClient):
|
|
|
77
81
|
raise_scim_errors: Optional[bool] = None,
|
|
78
82
|
**kwargs,
|
|
79
83
|
) -> Union[AnyResource, Error, dict]:
|
|
80
|
-
req = self.
|
|
84
|
+
req = self._prepare_create_request(
|
|
81
85
|
resource=resource,
|
|
82
86
|
check_request_payload=check_request_payload,
|
|
83
87
|
expected_status_codes=expected_status_codes,
|
|
84
|
-
raise_scim_errors=raise_scim_errors,
|
|
85
88
|
**kwargs,
|
|
86
89
|
)
|
|
87
90
|
|
|
@@ -102,7 +105,7 @@ class SyncSCIMClient(BaseSyncSCIMClient):
|
|
|
102
105
|
|
|
103
106
|
def query(
|
|
104
107
|
self,
|
|
105
|
-
resource_model: Optional[type[
|
|
108
|
+
resource_model: Optional[type[AnyResource]] = None,
|
|
106
109
|
id: Optional[str] = None,
|
|
107
110
|
search_request: Optional[Union[SearchRequest, dict]] = None,
|
|
108
111
|
check_request_payload: Optional[bool] = None,
|
|
@@ -112,14 +115,13 @@ class SyncSCIMClient(BaseSyncSCIMClient):
|
|
|
112
115
|
] = BaseSyncSCIMClient.QUERY_RESPONSE_STATUS_CODES,
|
|
113
116
|
raise_scim_errors: Optional[bool] = None,
|
|
114
117
|
**kwargs,
|
|
115
|
-
):
|
|
116
|
-
req = self.
|
|
118
|
+
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
119
|
+
req = self._prepare_query_request(
|
|
117
120
|
resource_model=resource_model,
|
|
118
121
|
id=id,
|
|
119
122
|
search_request=search_request,
|
|
120
123
|
check_request_payload=check_request_payload,
|
|
121
124
|
expected_status_codes=expected_status_codes,
|
|
122
|
-
raise_scim_errors=raise_scim_errors,
|
|
123
125
|
**kwargs,
|
|
124
126
|
)
|
|
125
127
|
|
|
@@ -151,11 +153,10 @@ class SyncSCIMClient(BaseSyncSCIMClient):
|
|
|
151
153
|
raise_scim_errors: Optional[bool] = None,
|
|
152
154
|
**kwargs,
|
|
153
155
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
154
|
-
req = self.
|
|
156
|
+
req = self._prepare_search_request(
|
|
155
157
|
search_request=search_request,
|
|
156
158
|
check_request_payload=check_request_payload,
|
|
157
159
|
expected_status_codes=expected_status_codes,
|
|
158
|
-
raise_scim_errors=raise_scim_errors,
|
|
159
160
|
**kwargs,
|
|
160
161
|
)
|
|
161
162
|
|
|
@@ -185,11 +186,10 @@ class SyncSCIMClient(BaseSyncSCIMClient):
|
|
|
185
186
|
raise_scim_errors: Optional[bool] = None,
|
|
186
187
|
**kwargs,
|
|
187
188
|
) -> Optional[Union[Error, dict]]:
|
|
188
|
-
req = self.
|
|
189
|
+
req = self._prepare_delete_request(
|
|
189
190
|
resource_model=resource_model,
|
|
190
191
|
id=id,
|
|
191
192
|
expected_status_codes=expected_status_codes,
|
|
192
|
-
raise_scim_errors=raise_scim_errors,
|
|
193
193
|
**kwargs,
|
|
194
194
|
)
|
|
195
195
|
|
|
@@ -217,11 +217,10 @@ class SyncSCIMClient(BaseSyncSCIMClient):
|
|
|
217
217
|
raise_scim_errors: Optional[bool] = None,
|
|
218
218
|
**kwargs,
|
|
219
219
|
) -> Union[AnyResource, Error, dict]:
|
|
220
|
-
req = self.
|
|
220
|
+
req = self._prepare_replace_request(
|
|
221
221
|
resource=resource,
|
|
222
222
|
check_request_payload=check_request_payload,
|
|
223
223
|
expected_status_codes=expected_status_codes,
|
|
224
|
-
raise_scim_errors=raise_scim_errors,
|
|
225
224
|
**kwargs,
|
|
226
225
|
)
|
|
227
226
|
|
|
@@ -240,6 +239,45 @@ class SyncSCIMClient(BaseSyncSCIMClient):
|
|
|
240
239
|
scim_ctx=Context.RESOURCE_REPLACEMENT_RESPONSE,
|
|
241
240
|
)
|
|
242
241
|
|
|
242
|
+
def modify(
|
|
243
|
+
self,
|
|
244
|
+
resource_model: type[ResourceT],
|
|
245
|
+
id: str,
|
|
246
|
+
patch_op: Union[PatchOp[ResourceT], dict],
|
|
247
|
+
check_request_payload: Optional[bool] = None,
|
|
248
|
+
check_response_payload: Optional[bool] = None,
|
|
249
|
+
expected_status_codes: Optional[
|
|
250
|
+
list[int]
|
|
251
|
+
] = BaseSyncSCIMClient.PATCH_RESPONSE_STATUS_CODES,
|
|
252
|
+
raise_scim_errors: Optional[bool] = None,
|
|
253
|
+
**kwargs,
|
|
254
|
+
) -> Optional[Union[ResourceT, Error, dict]]:
|
|
255
|
+
req = self._prepare_patch_request(
|
|
256
|
+
resource_model=resource_model,
|
|
257
|
+
id=id,
|
|
258
|
+
patch_op=patch_op,
|
|
259
|
+
check_request_payload=check_request_payload,
|
|
260
|
+
expected_status_codes=expected_status_codes,
|
|
261
|
+
**kwargs,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
with handle_request_error(req.payload):
|
|
265
|
+
response = self.client.patch(
|
|
266
|
+
req.url, json=req.payload, **req.request_kwargs
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
with handle_response_error(response):
|
|
270
|
+
return self.check_response(
|
|
271
|
+
payload=response.json() if response.text else None,
|
|
272
|
+
status_code=response.status_code,
|
|
273
|
+
headers=response.headers,
|
|
274
|
+
expected_status_codes=req.expected_status_codes,
|
|
275
|
+
expected_types=req.expected_types,
|
|
276
|
+
check_response_payload=check_response_payload,
|
|
277
|
+
raise_scim_errors=raise_scim_errors,
|
|
278
|
+
scim_ctx=Context.RESOURCE_PATCH_RESPONSE,
|
|
279
|
+
)
|
|
280
|
+
|
|
243
281
|
|
|
244
282
|
class AsyncSCIMClient(BaseAsyncSCIMClient):
|
|
245
283
|
"""Perform SCIM requests over the network and validate responses.
|
|
@@ -273,11 +311,10 @@ class AsyncSCIMClient(BaseAsyncSCIMClient):
|
|
|
273
311
|
raise_scim_errors: Optional[bool] = None,
|
|
274
312
|
**kwargs,
|
|
275
313
|
) -> Union[AnyResource, Error, dict]:
|
|
276
|
-
req = self.
|
|
314
|
+
req = self._prepare_create_request(
|
|
277
315
|
resource=resource,
|
|
278
316
|
check_request_payload=check_request_payload,
|
|
279
317
|
expected_status_codes=expected_status_codes,
|
|
280
|
-
raise_scim_errors=raise_scim_errors,
|
|
281
318
|
**kwargs,
|
|
282
319
|
)
|
|
283
320
|
|
|
@@ -310,14 +347,13 @@ class AsyncSCIMClient(BaseAsyncSCIMClient):
|
|
|
310
347
|
] = BaseAsyncSCIMClient.QUERY_RESPONSE_STATUS_CODES,
|
|
311
348
|
raise_scim_errors: Optional[bool] = None,
|
|
312
349
|
**kwargs,
|
|
313
|
-
):
|
|
314
|
-
req = self.
|
|
350
|
+
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
351
|
+
req = self._prepare_query_request(
|
|
315
352
|
resource_model=resource_model,
|
|
316
353
|
id=id,
|
|
317
354
|
search_request=search_request,
|
|
318
355
|
check_request_payload=check_request_payload,
|
|
319
356
|
expected_status_codes=expected_status_codes,
|
|
320
|
-
raise_scim_errors=raise_scim_errors,
|
|
321
357
|
**kwargs,
|
|
322
358
|
)
|
|
323
359
|
|
|
@@ -349,11 +385,10 @@ class AsyncSCIMClient(BaseAsyncSCIMClient):
|
|
|
349
385
|
raise_scim_errors: Optional[bool] = None,
|
|
350
386
|
**kwargs,
|
|
351
387
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
352
|
-
req = self.
|
|
388
|
+
req = self._prepare_search_request(
|
|
353
389
|
search_request=search_request,
|
|
354
390
|
check_request_payload=check_request_payload,
|
|
355
391
|
expected_status_codes=expected_status_codes,
|
|
356
|
-
raise_scim_errors=raise_scim_errors,
|
|
357
392
|
**kwargs,
|
|
358
393
|
)
|
|
359
394
|
|
|
@@ -385,11 +420,10 @@ class AsyncSCIMClient(BaseAsyncSCIMClient):
|
|
|
385
420
|
raise_scim_errors: Optional[bool] = None,
|
|
386
421
|
**kwargs,
|
|
387
422
|
) -> Optional[Union[Error, dict]]:
|
|
388
|
-
req = self.
|
|
423
|
+
req = self._prepare_delete_request(
|
|
389
424
|
resource_model=resource_model,
|
|
390
425
|
id=id,
|
|
391
426
|
expected_status_codes=expected_status_codes,
|
|
392
|
-
raise_scim_errors=raise_scim_errors,
|
|
393
427
|
**kwargs,
|
|
394
428
|
)
|
|
395
429
|
|
|
@@ -417,11 +451,10 @@ class AsyncSCIMClient(BaseAsyncSCIMClient):
|
|
|
417
451
|
raise_scim_errors: Optional[bool] = None,
|
|
418
452
|
**kwargs,
|
|
419
453
|
) -> Union[AnyResource, Error, dict]:
|
|
420
|
-
req = self.
|
|
454
|
+
req = self._prepare_replace_request(
|
|
421
455
|
resource=resource,
|
|
422
456
|
check_request_payload=check_request_payload,
|
|
423
457
|
expected_status_codes=expected_status_codes,
|
|
424
|
-
raise_scim_errors=raise_scim_errors,
|
|
425
458
|
**kwargs,
|
|
426
459
|
)
|
|
427
460
|
|
|
@@ -441,3 +474,42 @@ class AsyncSCIMClient(BaseAsyncSCIMClient):
|
|
|
441
474
|
raise_scim_errors=raise_scim_errors,
|
|
442
475
|
scim_ctx=Context.RESOURCE_REPLACEMENT_RESPONSE,
|
|
443
476
|
)
|
|
477
|
+
|
|
478
|
+
async def modify(
|
|
479
|
+
self,
|
|
480
|
+
resource_model: type[ResourceT],
|
|
481
|
+
id: str,
|
|
482
|
+
patch_op: Union[PatchOp[ResourceT], dict],
|
|
483
|
+
check_request_payload: Optional[bool] = None,
|
|
484
|
+
check_response_payload: Optional[bool] = None,
|
|
485
|
+
expected_status_codes: Optional[
|
|
486
|
+
list[int]
|
|
487
|
+
] = BaseAsyncSCIMClient.PATCH_RESPONSE_STATUS_CODES,
|
|
488
|
+
raise_scim_errors: Optional[bool] = None,
|
|
489
|
+
**kwargs,
|
|
490
|
+
) -> Optional[Union[ResourceT, Error, dict]]:
|
|
491
|
+
req = self._prepare_patch_request(
|
|
492
|
+
resource_model=resource_model,
|
|
493
|
+
id=id,
|
|
494
|
+
patch_op=patch_op,
|
|
495
|
+
check_request_payload=check_request_payload,
|
|
496
|
+
expected_status_codes=expected_status_codes,
|
|
497
|
+
**kwargs,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
with handle_request_error(req.payload):
|
|
501
|
+
response = await self.client.patch(
|
|
502
|
+
req.url, json=req.payload, **req.request_kwargs
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
with handle_response_error(response):
|
|
506
|
+
return self.check_response(
|
|
507
|
+
payload=response.json() if response.text else None,
|
|
508
|
+
status_code=response.status_code,
|
|
509
|
+
headers=response.headers,
|
|
510
|
+
expected_status_codes=req.expected_status_codes,
|
|
511
|
+
expected_types=req.expected_types,
|
|
512
|
+
check_response_payload=check_response_payload,
|
|
513
|
+
raise_scim_errors=raise_scim_errors,
|
|
514
|
+
scim_ctx=Context.RESOURCE_PATCH_RESPONSE,
|
|
515
|
+
)
|
scim2_client/engines/werkzeug.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from contextlib import contextmanager
|
|
3
3
|
from typing import Optional
|
|
4
|
+
from typing import TypeVar
|
|
4
5
|
from typing import Union
|
|
5
6
|
from urllib.parse import urlencode
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ from scim2_models import AnyResource
|
|
|
8
9
|
from scim2_models import Context
|
|
9
10
|
from scim2_models import Error
|
|
10
11
|
from scim2_models import ListResponse
|
|
12
|
+
from scim2_models import PatchOp
|
|
11
13
|
from scim2_models import Resource
|
|
12
14
|
from scim2_models import SearchRequest
|
|
13
15
|
from werkzeug.test import Client
|
|
@@ -16,6 +18,8 @@ from scim2_client.client import BaseSyncSCIMClient
|
|
|
16
18
|
from scim2_client.errors import SCIMClientError
|
|
17
19
|
from scim2_client.errors import UnexpectedContentFormat
|
|
18
20
|
|
|
21
|
+
ResourceT = TypeVar("ResourceT", bound=Resource)
|
|
22
|
+
|
|
19
23
|
|
|
20
24
|
@contextmanager
|
|
21
25
|
def handle_response_error(response):
|
|
@@ -86,7 +90,7 @@ class TestSCIMClient(BaseSyncSCIMClient):
|
|
|
86
90
|
self.scim_prefix = scim_prefix
|
|
87
91
|
self.environ = environ or {}
|
|
88
92
|
|
|
89
|
-
def
|
|
93
|
+
def _make_url(self, url: Optional[str]) -> str:
|
|
90
94
|
url = url or ""
|
|
91
95
|
prefix = (
|
|
92
96
|
self.scim_prefix[:-1]
|
|
@@ -110,16 +114,17 @@ class TestSCIMClient(BaseSyncSCIMClient):
|
|
|
110
114
|
raise_scim_errors: Optional[bool] = None,
|
|
111
115
|
**kwargs,
|
|
112
116
|
) -> Union[AnyResource, Error, dict]:
|
|
113
|
-
req = self.
|
|
117
|
+
req = self._prepare_create_request(
|
|
114
118
|
resource=resource,
|
|
115
119
|
check_request_payload=check_request_payload,
|
|
116
120
|
expected_status_codes=expected_status_codes,
|
|
117
|
-
raise_scim_errors=raise_scim_errors,
|
|
118
121
|
**kwargs,
|
|
119
122
|
)
|
|
120
123
|
|
|
121
124
|
environ = {**self.environ, **req.request_kwargs}
|
|
122
|
-
response = self.client.post(
|
|
125
|
+
response = self.client.post(
|
|
126
|
+
self._make_url(req.url), json=req.payload, **environ
|
|
127
|
+
)
|
|
123
128
|
|
|
124
129
|
with handle_response_error(req.payload):
|
|
125
130
|
return self.check_response(
|
|
@@ -135,7 +140,7 @@ class TestSCIMClient(BaseSyncSCIMClient):
|
|
|
135
140
|
|
|
136
141
|
def query(
|
|
137
142
|
self,
|
|
138
|
-
resource_model: Optional[type[
|
|
143
|
+
resource_model: Optional[type[AnyResource]] = None,
|
|
139
144
|
id: Optional[str] = None,
|
|
140
145
|
search_request: Optional[Union[SearchRequest, dict]] = None,
|
|
141
146
|
check_request_payload: Optional[bool] = None,
|
|
@@ -145,21 +150,20 @@ class TestSCIMClient(BaseSyncSCIMClient):
|
|
|
145
150
|
] = BaseSyncSCIMClient.QUERY_RESPONSE_STATUS_CODES,
|
|
146
151
|
raise_scim_errors: Optional[bool] = None,
|
|
147
152
|
**kwargs,
|
|
148
|
-
):
|
|
149
|
-
req = self.
|
|
153
|
+
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
154
|
+
req = self._prepare_query_request(
|
|
150
155
|
resource_model=resource_model,
|
|
151
156
|
id=id,
|
|
152
157
|
search_request=search_request,
|
|
153
158
|
check_request_payload=check_request_payload,
|
|
154
159
|
expected_status_codes=expected_status_codes,
|
|
155
|
-
raise_scim_errors=raise_scim_errors,
|
|
156
160
|
**kwargs,
|
|
157
161
|
)
|
|
158
162
|
|
|
159
163
|
query_string = urlencode(req.payload, doseq=False) if req.payload else None
|
|
160
164
|
environ = {**self.environ, **req.request_kwargs}
|
|
161
165
|
response = self.client.get(
|
|
162
|
-
self.
|
|
166
|
+
self._make_url(req.url), query_string=query_string, **environ
|
|
163
167
|
)
|
|
164
168
|
|
|
165
169
|
with handle_response_error(req.payload):
|
|
@@ -185,16 +189,17 @@ class TestSCIMClient(BaseSyncSCIMClient):
|
|
|
185
189
|
raise_scim_errors: Optional[bool] = None,
|
|
186
190
|
**kwargs,
|
|
187
191
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
188
|
-
req = self.
|
|
192
|
+
req = self._prepare_search_request(
|
|
189
193
|
search_request=search_request,
|
|
190
194
|
check_request_payload=check_request_payload,
|
|
191
195
|
expected_status_codes=expected_status_codes,
|
|
192
|
-
raise_scim_errors=raise_scim_errors,
|
|
193
196
|
**kwargs,
|
|
194
197
|
)
|
|
195
198
|
|
|
196
199
|
environ = {**self.environ, **req.request_kwargs}
|
|
197
|
-
response = self.client.post(
|
|
200
|
+
response = self.client.post(
|
|
201
|
+
self._make_url(req.url), json=req.payload, **environ
|
|
202
|
+
)
|
|
198
203
|
|
|
199
204
|
with handle_response_error(response):
|
|
200
205
|
return self.check_response(
|
|
@@ -219,16 +224,15 @@ class TestSCIMClient(BaseSyncSCIMClient):
|
|
|
219
224
|
raise_scim_errors: Optional[bool] = None,
|
|
220
225
|
**kwargs,
|
|
221
226
|
) -> Optional[Union[Error, dict]]:
|
|
222
|
-
req = self.
|
|
227
|
+
req = self._prepare_delete_request(
|
|
223
228
|
resource_model=resource_model,
|
|
224
229
|
id=id,
|
|
225
230
|
expected_status_codes=expected_status_codes,
|
|
226
|
-
raise_scim_errors=raise_scim_errors,
|
|
227
231
|
**kwargs,
|
|
228
232
|
)
|
|
229
233
|
|
|
230
234
|
environ = {**self.environ, **req.request_kwargs}
|
|
231
|
-
response = self.client.delete(self.
|
|
235
|
+
response = self.client.delete(self._make_url(req.url), **environ)
|
|
232
236
|
|
|
233
237
|
with handle_response_error(response):
|
|
234
238
|
return self.check_response(
|
|
@@ -251,16 +255,15 @@ class TestSCIMClient(BaseSyncSCIMClient):
|
|
|
251
255
|
raise_scim_errors: Optional[bool] = None,
|
|
252
256
|
**kwargs,
|
|
253
257
|
) -> Union[AnyResource, Error, dict]:
|
|
254
|
-
req = self.
|
|
258
|
+
req = self._prepare_replace_request(
|
|
255
259
|
resource=resource,
|
|
256
260
|
check_request_payload=check_request_payload,
|
|
257
261
|
expected_status_codes=expected_status_codes,
|
|
258
|
-
raise_scim_errors=raise_scim_errors,
|
|
259
262
|
**kwargs,
|
|
260
263
|
)
|
|
261
264
|
|
|
262
265
|
environ = {**self.environ, **req.request_kwargs}
|
|
263
|
-
response = self.client.put(self.
|
|
266
|
+
response = self.client.put(self._make_url(req.url), json=req.payload, **environ)
|
|
264
267
|
|
|
265
268
|
with handle_response_error(response):
|
|
266
269
|
return self.check_response(
|
|
@@ -273,3 +276,42 @@ class TestSCIMClient(BaseSyncSCIMClient):
|
|
|
273
276
|
raise_scim_errors=raise_scim_errors,
|
|
274
277
|
scim_ctx=Context.RESOURCE_REPLACEMENT_RESPONSE,
|
|
275
278
|
)
|
|
279
|
+
|
|
280
|
+
def modify(
|
|
281
|
+
self,
|
|
282
|
+
resource_model: type[ResourceT],
|
|
283
|
+
id: str,
|
|
284
|
+
patch_op: Union[PatchOp[ResourceT], dict],
|
|
285
|
+
check_request_payload: Optional[bool] = None,
|
|
286
|
+
check_response_payload: Optional[bool] = None,
|
|
287
|
+
expected_status_codes: Optional[
|
|
288
|
+
list[int]
|
|
289
|
+
] = BaseSyncSCIMClient.PATCH_RESPONSE_STATUS_CODES,
|
|
290
|
+
raise_scim_errors: Optional[bool] = None,
|
|
291
|
+
**kwargs,
|
|
292
|
+
) -> Optional[Union[ResourceT, Error, dict]]:
|
|
293
|
+
req = self._prepare_patch_request(
|
|
294
|
+
resource_model=resource_model,
|
|
295
|
+
id=id,
|
|
296
|
+
patch_op=patch_op,
|
|
297
|
+
check_request_payload=check_request_payload,
|
|
298
|
+
expected_status_codes=expected_status_codes,
|
|
299
|
+
**kwargs,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
environ = {**self.environ, **req.request_kwargs}
|
|
303
|
+
response = self.client.patch(
|
|
304
|
+
self._make_url(req.url), json=req.payload, **environ
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
with handle_response_error(response):
|
|
308
|
+
return self.check_response(
|
|
309
|
+
payload=response.json if response.text else None,
|
|
310
|
+
status_code=response.status_code,
|
|
311
|
+
headers=response.headers,
|
|
312
|
+
expected_status_codes=req.expected_status_codes,
|
|
313
|
+
expected_types=req.expected_types,
|
|
314
|
+
check_response_payload=check_response_payload,
|
|
315
|
+
raise_scim_errors=raise_scim_errors,
|
|
316
|
+
scim_ctx=Context.RESOURCE_PATCH_RESPONSE,
|
|
317
|
+
)
|
scim2_client/errors.py
CHANGED
|
@@ -9,12 +9,14 @@ class SCIMClientError(Exception):
|
|
|
9
9
|
caused the exception.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
def __init__(
|
|
12
|
+
def __init__(
|
|
13
|
+
self, message: str, source: Any = None, *args: Any, **kwargs: Any
|
|
14
|
+
) -> None:
|
|
13
15
|
self.message = message
|
|
14
16
|
self.source = source
|
|
15
17
|
super().__init__(*args, **kwargs)
|
|
16
18
|
|
|
17
|
-
def __str__(self):
|
|
19
|
+
def __str__(self) -> str:
|
|
18
20
|
return self.message or "UNKNOWN"
|
|
19
21
|
|
|
20
22
|
|
|
@@ -29,7 +31,7 @@ class RequestNetworkError(SCIMRequestError):
|
|
|
29
31
|
The original :class:`~httpx.RequestError` is available with :attr:`~BaseException.__cause__`.
|
|
30
32
|
"""
|
|
31
33
|
|
|
32
|
-
def __init__(self, *args, **kwargs):
|
|
34
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
33
35
|
message = kwargs.pop("message", "Network error happened during request")
|
|
34
36
|
super().__init__(message, *args, **kwargs)
|
|
35
37
|
|
|
@@ -54,7 +56,7 @@ class RequestPayloadValidationError(SCIMRequestError):
|
|
|
54
56
|
print("Original validation error cause", exc.__cause__)
|
|
55
57
|
"""
|
|
56
58
|
|
|
57
|
-
def __init__(self, *args, **kwargs):
|
|
59
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
58
60
|
message = kwargs.pop("message", "Server request payload validation error")
|
|
59
61
|
super().__init__(message, *args, **kwargs)
|
|
60
62
|
|
|
@@ -69,7 +71,7 @@ class SCIMResponseErrorObject(SCIMResponseError):
|
|
|
69
71
|
Those errors are only raised when the :code:`raise_scim_errors` parameter is :data:`True`.
|
|
70
72
|
"""
|
|
71
73
|
|
|
72
|
-
def __init__(self, obj, *args, **kwargs):
|
|
74
|
+
def __init__(self, obj: Any, *args: Any, **kwargs: Any) -> None:
|
|
73
75
|
message = kwargs.pop(
|
|
74
76
|
"message", f"The server returned a SCIM Error object: {obj}"
|
|
75
77
|
)
|
|
@@ -79,7 +81,7 @@ class SCIMResponseErrorObject(SCIMResponseError):
|
|
|
79
81
|
class UnexpectedStatusCode(SCIMResponseError):
|
|
80
82
|
"""Error raised when a server returned an unexpected status code for a given :class:`~scim2_models.Context`."""
|
|
81
83
|
|
|
82
|
-
def __init__(self, status_code: int, *args, **kwargs):
|
|
84
|
+
def __init__(self, status_code: int, *args: Any, **kwargs: Any) -> None:
|
|
83
85
|
message = kwargs.pop(
|
|
84
86
|
"message", f"Unexpected response status code: {status_code}"
|
|
85
87
|
)
|
|
@@ -89,7 +91,7 @@ class UnexpectedStatusCode(SCIMResponseError):
|
|
|
89
91
|
class UnexpectedContentType(SCIMResponseError):
|
|
90
92
|
"""Error raised when a server returned an unexpected `Content-Type` header in a response."""
|
|
91
93
|
|
|
92
|
-
def __init__(self, content_type, *args, **kwargs):
|
|
94
|
+
def __init__(self, content_type: str, *args: Any, **kwargs: Any) -> None:
|
|
93
95
|
message = kwargs.pop("message", f"Unexpected content type: {content_type}")
|
|
94
96
|
super().__init__(message, *args, **kwargs)
|
|
95
97
|
|
|
@@ -97,7 +99,7 @@ class UnexpectedContentType(SCIMResponseError):
|
|
|
97
99
|
class UnexpectedContentFormat(SCIMResponseError):
|
|
98
100
|
"""Error raised when a server returned a response in a non-JSON format."""
|
|
99
101
|
|
|
100
|
-
def __init__(self, *args, **kwargs):
|
|
102
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
101
103
|
message = kwargs.pop("message", "Unexpected response content format")
|
|
102
104
|
super().__init__(message, *args, **kwargs)
|
|
103
105
|
|
|
@@ -117,6 +119,6 @@ class ResponsePayloadValidationError(SCIMResponseError):
|
|
|
117
119
|
print("Original validation error cause", exc.__cause__)
|
|
118
120
|
"""
|
|
119
121
|
|
|
120
|
-
def __init__(self, *args, **kwargs):
|
|
122
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
121
123
|
message = kwargs.pop("message", "Server response payload validation error")
|
|
122
124
|
super().__init__(message, *args, **kwargs)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scim2-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Pythonically build SCIM requests and parse SCIM responses
|
|
5
5
|
Project-URL: documentation, https://scim2-client.readthedocs.io
|
|
6
6
|
Project-URL: repository, https://github.com/python-scim/scim2-client
|
|
@@ -223,7 +223,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
223
223
|
Classifier: Programming Language :: Python :: 3.13
|
|
224
224
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
225
225
|
Requires-Python: >=3.9
|
|
226
|
-
Requires-Dist: scim2-models>=0.
|
|
226
|
+
Requires-Dist: scim2-models>=0.4.1
|
|
227
227
|
Provides-Extra: httpx
|
|
228
228
|
Requires-Dist: httpx>=0.28.0; extra == 'httpx'
|
|
229
229
|
Provides-Extra: werkzeug
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
scim2_client/__init__.py,sha256=l0pyBLiTpFA68ao98PqQLT_Xx0mw8BHumwrIHYCWa_M,845
|
|
2
|
+
scim2_client/client.py,sha256=jtL0Lhy8USPECiAwVbD5grIVePFIIdfibVu0_5EQISQ,55067
|
|
3
|
+
scim2_client/errors.py,sha256=LTTrjwTImKcPmeEcd_WqSphaGNf_2tlRA4V7UhndAE8,4572
|
|
4
|
+
scim2_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
scim2_client/engines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
scim2_client/engines/httpx.py,sha256=vpMa6_1q8RsyZo1hCm8FOt5htbgiEW58LZ5EwkMu82s,20535
|
|
7
|
+
scim2_client/engines/werkzeug.py,sha256=mszoQSneEH2SsewcG7Pq6hW-zFIeyreObFuO2K89ssc,12277
|
|
8
|
+
scim2_client-0.6.1.dist-info/METADATA,sha256=SVVX7EcdbqP_9jF9yS1CB2dsNrGidGKNctJAQ0U81Ws,16986
|
|
9
|
+
scim2_client-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
+
scim2_client-0.6.1.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
11
|
+
scim2_client-0.6.1.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
scim2_client/__init__.py,sha256=l0pyBLiTpFA68ao98PqQLT_Xx0mw8BHumwrIHYCWa_M,845
|
|
2
|
-
scim2_client/client.py,sha256=jSj_g8qjpCFF1sKd-STJNPjfdHN_zjGAkYBes9CveOY,46374
|
|
3
|
-
scim2_client/errors.py,sha256=FVmRXsaZLn1VZhJ3dSDs4IqycuU92AEun9JWWMseVO8,4397
|
|
4
|
-
scim2_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
scim2_client/engines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
scim2_client/engines/httpx.py,sha256=L39ZZHjqe43uIQpgh_r_maqM2gkf3_Rk6d8MeNe57gk,17781
|
|
7
|
-
scim2_client/engines/werkzeug.py,sha256=DmUBWytoWPehcrgBpGzlRJLCT7mrAW_VWSQ6BHylwCo,10800
|
|
8
|
-
scim2_client-0.5.2.dist-info/METADATA,sha256=hmKozbJhq6eZxe_rFfspw-hHsD9H1pQmfsHMm_CTTdk,16986
|
|
9
|
-
scim2_client-0.5.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
-
scim2_client-0.5.2.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
11
|
-
scim2_client-0.5.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|