scim2-client 0.3.2__py3-none-any.whl → 0.4.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 +2 -2
- scim2_client/client.py +143 -57
- scim2_client/engines/werkzeug.py +8 -0
- scim2_client/errors.py +1 -1
- {scim2_client-0.3.2.dist-info → scim2_client-0.4.0.dist-info}/METADATA +9 -6
- scim2_client-0.4.0.dist-info/RECORD +11 -0
- scim2_client-0.3.2.dist-info/RECORD +0 -11
- {scim2_client-0.3.2.dist-info → scim2_client-0.4.0.dist-info}/WHEEL +0 -0
- {scim2_client-0.3.2.dist-info → scim2_client-0.4.0.dist-info}/licenses/LICENSE.md +0 -0
scim2_client/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
from .client import BaseSCIMClient
|
|
2
1
|
from .client import BaseSyncSCIMClient
|
|
2
|
+
from .client import SCIMClient
|
|
3
3
|
from .errors import RequestNetworkError
|
|
4
4
|
from .errors import RequestPayloadValidationError
|
|
5
5
|
from .errors import ResponsePayloadValidationError
|
|
@@ -12,7 +12,7 @@ from .errors import UnexpectedContentType
|
|
|
12
12
|
from .errors import UnexpectedStatusCode
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
|
-
"
|
|
15
|
+
"SCIMClient",
|
|
16
16
|
"BaseSyncSCIMClient",
|
|
17
17
|
"SCIMClientError",
|
|
18
18
|
"SCIMRequestError",
|
scim2_client/client.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import sys
|
|
3
|
+
from collections.abc import Collection
|
|
2
4
|
from dataclasses import dataclass
|
|
3
5
|
from typing import Optional
|
|
4
6
|
from typing import Union
|
|
@@ -7,6 +9,7 @@ from pydantic import ValidationError
|
|
|
7
9
|
from scim2_models import AnyResource
|
|
8
10
|
from scim2_models import Context
|
|
9
11
|
from scim2_models import Error
|
|
12
|
+
from scim2_models import Extension
|
|
10
13
|
from scim2_models import ListResponse
|
|
11
14
|
from scim2_models import PatchOp
|
|
12
15
|
from scim2_models import Resource
|
|
@@ -28,6 +31,7 @@ BASE_HEADERS = {
|
|
|
28
31
|
"Accept": "application/scim+json",
|
|
29
32
|
"Content-Type": "application/scim+json",
|
|
30
33
|
}
|
|
34
|
+
CONFIG_RESOURCES = (ResourceType, Schema, ServiceProviderConfig)
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
@dataclass
|
|
@@ -39,15 +43,18 @@ class RequestPayload:
|
|
|
39
43
|
expected_status_codes: Optional[list[int]] = None
|
|
40
44
|
|
|
41
45
|
|
|
42
|
-
class
|
|
46
|
+
class SCIMClient:
|
|
43
47
|
"""The base model for request clients.
|
|
44
48
|
|
|
45
49
|
It goal is to parse the requests and responses and check if they comply with the SCIM specifications.
|
|
46
50
|
|
|
47
51
|
This class can be inherited and used as a basis for request engine integration.
|
|
48
52
|
|
|
49
|
-
:param resource_models: A
|
|
53
|
+
:param resource_models: A collection of :class:`~scim2_models.Resource` models expected to be handled by the SCIM client.
|
|
50
54
|
If a request payload describe a resource that is not in this list, an exception will be raised.
|
|
55
|
+
:param resource_types: A collection of :class:`~scim2_models.ResourceType` that will be used to guess the
|
|
56
|
+
server endpoints associated with the resources.
|
|
57
|
+
:param service_provider_config: An instance of :class:`~scim2_models.ServiceProviderConfig`.
|
|
51
58
|
:param check_request_payload: If :data:`False`,
|
|
52
59
|
:code:`resource` is expected to be a dict that will be passed as-is in the request.
|
|
53
60
|
This value can be overwritten in methods.
|
|
@@ -144,40 +151,74 @@ class BaseSCIMClient:
|
|
|
144
151
|
|
|
145
152
|
def __init__(
|
|
146
153
|
self,
|
|
147
|
-
resource_models: Optional[
|
|
154
|
+
resource_models: Optional[Collection[type[Resource]]] = None,
|
|
155
|
+
resource_types: Optional[Collection[ResourceType]] = None,
|
|
156
|
+
service_provider_config: Optional[ServiceProviderConfig] = None,
|
|
148
157
|
check_request_payload: bool = True,
|
|
149
158
|
check_response_payload: bool = True,
|
|
150
159
|
raise_scim_errors: bool = True,
|
|
151
160
|
):
|
|
152
|
-
self.resource_models = tuple(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
self.check_request_payload =
|
|
156
|
-
self.check_response_payload =
|
|
157
|
-
self.raise_scim_errors =
|
|
161
|
+
self.resource_models = tuple(set(resource_models or []) | set(CONFIG_RESOURCES))
|
|
162
|
+
self.resource_types = resource_types
|
|
163
|
+
self.service_provider_config = service_provider_config
|
|
164
|
+
self.check_request_payload = check_request_payload
|
|
165
|
+
self.check_response_payload = check_response_payload
|
|
166
|
+
self.raise_scim_errors = raise_scim_errors
|
|
167
|
+
|
|
168
|
+
def get_resource_model(self, name: str) -> Optional[type[Resource]]:
|
|
169
|
+
"""Get a registered model by its name or its schema."""
|
|
170
|
+
for resource_model in self.resource_models:
|
|
171
|
+
schema = resource_model.model_fields["schemas"].default[0]
|
|
172
|
+
if schema == name or schema.split(":")[-1] == name:
|
|
173
|
+
return resource_model
|
|
174
|
+
return None
|
|
158
175
|
|
|
159
176
|
def check_resource_model(
|
|
160
177
|
self, resource_model: type[Resource], payload=None
|
|
161
178
|
) -> None:
|
|
162
|
-
if
|
|
179
|
+
if (
|
|
180
|
+
resource_model not in self.resource_models
|
|
181
|
+
and resource_model not in CONFIG_RESOURCES
|
|
182
|
+
):
|
|
163
183
|
raise SCIMRequestError(
|
|
164
184
|
f"Unknown resource type: '{resource_model}'", source=payload
|
|
165
185
|
)
|
|
166
186
|
|
|
167
187
|
def resource_endpoint(self, resource_model: Optional[type[Resource]]) -> str:
|
|
188
|
+
"""Find the :attr:`~scim2_models.ResourceType.endpoint` associated with a given :class:`~scim2_models.Resource`.
|
|
189
|
+
|
|
190
|
+
Internally, it looks if any :paramref:`resource_type <scim2_client.SCIMClient.resource_models>`
|
|
191
|
+
of the client matches the resource_model by comparing schemas.
|
|
192
|
+
"""
|
|
168
193
|
if resource_model is None:
|
|
169
194
|
return "/"
|
|
170
195
|
|
|
196
|
+
if resource_model in (ResourceType, Schema):
|
|
197
|
+
return f"/{resource_model.__name__}s"
|
|
198
|
+
|
|
171
199
|
# This one takes no final 's'
|
|
172
200
|
if resource_model is ServiceProviderConfig:
|
|
173
201
|
return "/ServiceProviderConfig"
|
|
174
202
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
203
|
+
schema = resource_model.model_fields["schemas"].default[0]
|
|
204
|
+
for resource_type in self.resource_types or []:
|
|
205
|
+
if schema == resource_type.schema_:
|
|
206
|
+
return resource_type.endpoint
|
|
207
|
+
|
|
208
|
+
raise SCIMRequestError(f"No ResourceType is matching the schema: {schema}")
|
|
209
|
+
|
|
210
|
+
def register_naive_resource_types(self):
|
|
211
|
+
"""Register a *naive* :class:`~scim2_models.ResourceType` for each :paramref:`resource_model <scim2_client.SCIMClient.resource_models>`.
|
|
212
|
+
|
|
213
|
+
This fills the :class:`~scim2_models.ResourceType` with generic values.
|
|
214
|
+
The endpoint is the resource name with a *s* suffix.
|
|
215
|
+
For instance, the :class:`~scim2_models.User` will have a `/Users` endpoint.
|
|
216
|
+
"""
|
|
217
|
+
self.resource_types = [
|
|
218
|
+
ResourceType.from_resource(model)
|
|
219
|
+
for model in self.resource_models
|
|
220
|
+
if model not in CONFIG_RESOURCES
|
|
221
|
+
]
|
|
181
222
|
|
|
182
223
|
def check_response(
|
|
183
224
|
self,
|
|
@@ -476,8 +517,32 @@ class BaseSCIMClient:
|
|
|
476
517
|
) -> Optional[Union[AnyResource, dict]]:
|
|
477
518
|
raise NotImplementedError()
|
|
478
519
|
|
|
479
|
-
|
|
480
|
-
|
|
520
|
+
def build_resource_models(
|
|
521
|
+
self, resource_types: Collection[ResourceType], schemas: Collection[Schema]
|
|
522
|
+
) -> tuple[type[Resource]]:
|
|
523
|
+
"""Build models from server objects."""
|
|
524
|
+
resource_types_by_schema = {
|
|
525
|
+
resource_type.schema_: resource_type for resource_type in resource_types
|
|
526
|
+
}
|
|
527
|
+
schema_objs_by_schema = {schema_obj.id: schema_obj for schema_obj in schemas}
|
|
528
|
+
|
|
529
|
+
resource_models = []
|
|
530
|
+
for schema, resource_type in resource_types_by_schema.items():
|
|
531
|
+
schema_obj = schema_objs_by_schema[schema]
|
|
532
|
+
model = Resource.from_schema(schema_obj)
|
|
533
|
+
extensions = []
|
|
534
|
+
for ext_schema in resource_type.schema_extensions or []:
|
|
535
|
+
schema_obj = schema_objs_by_schema[ext_schema.schema_]
|
|
536
|
+
extension = Extension.from_schema(schema_obj)
|
|
537
|
+
extensions.append(extension)
|
|
538
|
+
if extensions:
|
|
539
|
+
model = model[tuple(extensions)]
|
|
540
|
+
resource_models.append(model)
|
|
541
|
+
|
|
542
|
+
return tuple(resource_models)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
class BaseSyncSCIMClient(SCIMClient):
|
|
481
546
|
"""Base class for synchronous request clients."""
|
|
482
547
|
|
|
483
548
|
def create(
|
|
@@ -487,7 +552,7 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
487
552
|
check_response_payload: Optional[bool] = None,
|
|
488
553
|
expected_status_codes: Optional[
|
|
489
554
|
list[int]
|
|
490
|
-
] =
|
|
555
|
+
] = SCIMClient.CREATION_RESPONSE_STATUS_CODES,
|
|
491
556
|
raise_scim_errors: Optional[bool] = None,
|
|
492
557
|
**kwargs,
|
|
493
558
|
) -> Union[AnyResource, Error, dict]:
|
|
@@ -495,11 +560,11 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
495
560
|
|
|
496
561
|
:param resource: The resource to create
|
|
497
562
|
If is a :data:`dict`, the resource type will be guessed from the schema.
|
|
498
|
-
:param check_request_payload: If set, overwrites :paramref:`
|
|
499
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
563
|
+
:param check_request_payload: If set, overwrites :paramref:`SCIMClient.check_request_payload`.
|
|
564
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
500
565
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
501
566
|
If :data:`None` any status code is accepted.
|
|
502
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
567
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
503
568
|
:param kwargs: Additional parameters passed to the underlying HTTP request
|
|
504
569
|
library.
|
|
505
570
|
|
|
@@ -535,7 +600,7 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
535
600
|
check_response_payload: Optional[bool] = None,
|
|
536
601
|
expected_status_codes: Optional[
|
|
537
602
|
list[int]
|
|
538
|
-
] =
|
|
603
|
+
] = SCIMClient.QUERY_RESPONSE_STATUS_CODES,
|
|
539
604
|
raise_scim_errors: Optional[bool] = None,
|
|
540
605
|
**kwargs,
|
|
541
606
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
@@ -547,11 +612,11 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
547
612
|
:param resource_model: A :class:`~scim2_models.Resource` subtype or :data:`None`
|
|
548
613
|
:param id: The SCIM id of an object to get, or :data:`None`
|
|
549
614
|
:param search_request: An object detailing the search query parameters.
|
|
550
|
-
:param check_request_payload: If set, overwrites :paramref:`
|
|
551
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
615
|
+
:param check_request_payload: If set, overwrites :paramref:`SCIMClient.check_request_payload`.
|
|
616
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
552
617
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
553
618
|
If :data:`None` any status code is accepted.
|
|
554
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
619
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
555
620
|
:param kwargs: Additional parameters passed to the underlying HTTP request library.
|
|
556
621
|
|
|
557
622
|
:return:
|
|
@@ -607,7 +672,7 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
607
672
|
check_response_payload: Optional[bool] = None,
|
|
608
673
|
expected_status_codes: Optional[
|
|
609
674
|
list[int]
|
|
610
|
-
] =
|
|
675
|
+
] = SCIMClient.SEARCH_RESPONSE_STATUS_CODES,
|
|
611
676
|
raise_scim_errors: Optional[bool] = None,
|
|
612
677
|
**kwargs,
|
|
613
678
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
@@ -616,11 +681,11 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
616
681
|
:param resource_models: Resource type or union of types expected
|
|
617
682
|
to be read from the response.
|
|
618
683
|
:param search_request: An object detailing the search query parameters.
|
|
619
|
-
:param check_request_payload: If set, overwrites :paramref:`
|
|
620
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
684
|
+
:param check_request_payload: If set, overwrites :paramref:`SCIMClient.check_request_payload`.
|
|
685
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
621
686
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
622
687
|
If :data:`None` any status code is accepted.
|
|
623
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
688
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
624
689
|
:param kwargs: Additional parameters passed to the underlying
|
|
625
690
|
HTTP request library.
|
|
626
691
|
|
|
@@ -655,7 +720,7 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
655
720
|
check_response_payload: Optional[bool] = None,
|
|
656
721
|
expected_status_codes: Optional[
|
|
657
722
|
list[int]
|
|
658
|
-
] =
|
|
723
|
+
] = SCIMClient.DELETION_RESPONSE_STATUS_CODES,
|
|
659
724
|
raise_scim_errors: Optional[bool] = None,
|
|
660
725
|
**kwargs,
|
|
661
726
|
) -> Optional[Union[Error, dict]]:
|
|
@@ -663,10 +728,10 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
663
728
|
|
|
664
729
|
:param resource_model: The type of the resource to delete.
|
|
665
730
|
:param id: The type id the resource to delete.
|
|
666
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
731
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
667
732
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
668
733
|
If :data:`None` any status code is accepted.
|
|
669
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
734
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
670
735
|
:param kwargs: Additional parameters passed to the underlying
|
|
671
736
|
HTTP request library.
|
|
672
737
|
|
|
@@ -693,7 +758,7 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
693
758
|
check_response_payload: Optional[bool] = None,
|
|
694
759
|
expected_status_codes: Optional[
|
|
695
760
|
list[int]
|
|
696
|
-
] =
|
|
761
|
+
] = SCIMClient.REPLACEMENT_RESPONSE_STATUS_CODES,
|
|
697
762
|
raise_scim_errors: Optional[bool] = None,
|
|
698
763
|
**kwargs,
|
|
699
764
|
) -> Union[AnyResource, Error, dict]:
|
|
@@ -701,11 +766,11 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
701
766
|
|
|
702
767
|
:param resource: The new resource to replace.
|
|
703
768
|
If is a :data:`dict`, the resource type will be guessed from the schema.
|
|
704
|
-
:param check_request_payload: If set, overwrites :paramref:`
|
|
705
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
769
|
+
:param check_request_payload: If set, overwrites :paramref:`SCIMClient.check_request_payload`.
|
|
770
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
706
771
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
707
772
|
If :data:`None` any status code is accepted.
|
|
708
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
773
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
709
774
|
:param kwargs: Additional parameters passed to the underlying
|
|
710
775
|
HTTP request library.
|
|
711
776
|
|
|
@@ -733,8 +798,17 @@ class BaseSyncSCIMClient(BaseSCIMClient):
|
|
|
733
798
|
"""
|
|
734
799
|
raise NotImplementedError()
|
|
735
800
|
|
|
801
|
+
def discover(self):
|
|
802
|
+
"""Dynamically discover the server models :class:`~scim2_models.Schema` and :class:`~scim2_models.ResourceType`."""
|
|
803
|
+
resource_types_response = self.query(ResourceType)
|
|
804
|
+
schemas_response = self.query(Schema)
|
|
805
|
+
self.service_provider_config = self.query(ServiceProviderConfig)
|
|
806
|
+
self.resource_types = resource_types_response.resources
|
|
807
|
+
schemas = schemas_response.resources
|
|
808
|
+
self.resource_models = self.build_resource_models(self.resource_types, schemas)
|
|
809
|
+
|
|
736
810
|
|
|
737
|
-
class BaseAsyncSCIMClient(
|
|
811
|
+
class BaseAsyncSCIMClient(SCIMClient):
|
|
738
812
|
"""Base class for asynchronous request clients."""
|
|
739
813
|
|
|
740
814
|
async def create(
|
|
@@ -744,7 +818,7 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
744
818
|
check_response_payload: Optional[bool] = None,
|
|
745
819
|
expected_status_codes: Optional[
|
|
746
820
|
list[int]
|
|
747
|
-
] =
|
|
821
|
+
] = SCIMClient.CREATION_RESPONSE_STATUS_CODES,
|
|
748
822
|
raise_scim_errors: Optional[bool] = None,
|
|
749
823
|
**kwargs,
|
|
750
824
|
) -> Union[AnyResource, Error, dict]:
|
|
@@ -752,11 +826,11 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
752
826
|
|
|
753
827
|
:param resource: The resource to create
|
|
754
828
|
If is a :data:`dict`, the resource type will be guessed from the schema.
|
|
755
|
-
:param check_request_payload: If set, overwrites :paramref:`
|
|
756
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
829
|
+
:param check_request_payload: If set, overwrites :paramref:`SCIMClient.check_request_payload`.
|
|
830
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
757
831
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
758
832
|
If :data:`None` any status code is accepted.
|
|
759
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
833
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
760
834
|
:param kwargs: Additional parameters passed to the underlying HTTP request
|
|
761
835
|
library.
|
|
762
836
|
|
|
@@ -792,7 +866,7 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
792
866
|
check_response_payload: Optional[bool] = None,
|
|
793
867
|
expected_status_codes: Optional[
|
|
794
868
|
list[int]
|
|
795
|
-
] =
|
|
869
|
+
] = SCIMClient.QUERY_RESPONSE_STATUS_CODES,
|
|
796
870
|
raise_scim_errors: Optional[bool] = None,
|
|
797
871
|
**kwargs,
|
|
798
872
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
@@ -804,11 +878,11 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
804
878
|
:param resource_model: A :class:`~scim2_models.Resource` subtype or :data:`None`
|
|
805
879
|
:param id: The SCIM id of an object to get, or :data:`None`
|
|
806
880
|
:param search_request: An object detailing the search query parameters.
|
|
807
|
-
:param check_request_payload: If set, overwrites :paramref:`
|
|
808
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
881
|
+
:param check_request_payload: If set, overwrites :paramref:`SCIMClient.check_request_payload`.
|
|
882
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
809
883
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
810
884
|
If :data:`None` any status code is accepted.
|
|
811
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
885
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
812
886
|
:param kwargs: Additional parameters passed to the underlying HTTP request library.
|
|
813
887
|
|
|
814
888
|
:return:
|
|
@@ -864,7 +938,7 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
864
938
|
check_response_payload: Optional[bool] = None,
|
|
865
939
|
expected_status_codes: Optional[
|
|
866
940
|
list[int]
|
|
867
|
-
] =
|
|
941
|
+
] = SCIMClient.SEARCH_RESPONSE_STATUS_CODES,
|
|
868
942
|
raise_scim_errors: Optional[bool] = None,
|
|
869
943
|
**kwargs,
|
|
870
944
|
) -> Union[AnyResource, ListResponse[AnyResource], Error, dict]:
|
|
@@ -873,11 +947,11 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
873
947
|
:param resource_models: Resource type or union of types expected
|
|
874
948
|
to be read from the response.
|
|
875
949
|
:param search_request: An object detailing the search query parameters.
|
|
876
|
-
:param check_request_payload: If set, overwrites :paramref:`
|
|
877
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
950
|
+
:param check_request_payload: If set, overwrites :paramref:`SCIMClient.check_request_payload`.
|
|
951
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
878
952
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
879
953
|
If :data:`None` any status code is accepted.
|
|
880
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
954
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
881
955
|
:param kwargs: Additional parameters passed to the underlying
|
|
882
956
|
HTTP request library.
|
|
883
957
|
|
|
@@ -912,7 +986,7 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
912
986
|
check_response_payload: Optional[bool] = None,
|
|
913
987
|
expected_status_codes: Optional[
|
|
914
988
|
list[int]
|
|
915
|
-
] =
|
|
989
|
+
] = SCIMClient.DELETION_RESPONSE_STATUS_CODES,
|
|
916
990
|
raise_scim_errors: Optional[bool] = None,
|
|
917
991
|
**kwargs,
|
|
918
992
|
) -> Optional[Union[Error, dict]]:
|
|
@@ -920,10 +994,10 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
920
994
|
|
|
921
995
|
:param resource_model: The type of the resource to delete.
|
|
922
996
|
:param id: The type id the resource to delete.
|
|
923
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
997
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
924
998
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
925
999
|
If :data:`None` any status code is accepted.
|
|
926
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
1000
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
927
1001
|
:param kwargs: Additional parameters passed to the underlying
|
|
928
1002
|
HTTP request library.
|
|
929
1003
|
|
|
@@ -950,7 +1024,7 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
950
1024
|
check_response_payload: Optional[bool] = None,
|
|
951
1025
|
expected_status_codes: Optional[
|
|
952
1026
|
list[int]
|
|
953
|
-
] =
|
|
1027
|
+
] = SCIMClient.REPLACEMENT_RESPONSE_STATUS_CODES,
|
|
954
1028
|
raise_scim_errors: Optional[bool] = None,
|
|
955
1029
|
**kwargs,
|
|
956
1030
|
) -> Union[AnyResource, Error, dict]:
|
|
@@ -958,11 +1032,11 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
958
1032
|
|
|
959
1033
|
:param resource: The new resource to replace.
|
|
960
1034
|
If is a :data:`dict`, the resource type will be guessed from the schema.
|
|
961
|
-
:param check_request_payload: If set, overwrites :paramref:`
|
|
962
|
-
:param check_response_payload: If set, overwrites :paramref:`
|
|
1035
|
+
:param check_request_payload: If set, overwrites :paramref:`SCIMClient.check_request_payload`.
|
|
1036
|
+
:param check_response_payload: If set, overwrites :paramref:`SCIMClient.check_response_payload`.
|
|
963
1037
|
:param expected_status_codes: The list of expected status codes form the response.
|
|
964
1038
|
If :data:`None` any status code is accepted.
|
|
965
|
-
:param raise_scim_errors: If set, overwrites :paramref:`
|
|
1039
|
+
:param raise_scim_errors: If set, overwrites :paramref:`SCIMClient.raise_scim_errors`.
|
|
966
1040
|
:param kwargs: Additional parameters passed to the underlying
|
|
967
1041
|
HTTP request library.
|
|
968
1042
|
|
|
@@ -989,3 +1063,15 @@ class BaseAsyncSCIMClient(BaseSCIMClient):
|
|
|
989
1063
|
the response payload.
|
|
990
1064
|
"""
|
|
991
1065
|
raise NotImplementedError()
|
|
1066
|
+
|
|
1067
|
+
async def discover(self):
|
|
1068
|
+
"""Dynamically discover the server models :class:`~scim2_models.Schema` and :class:`~scim2_models.ResourceType`."""
|
|
1069
|
+
resources_task = asyncio.create_task(self.query(ResourceType))
|
|
1070
|
+
schemas_task = asyncio.create_task(self.query(Schema))
|
|
1071
|
+
spc_task = asyncio.create_task(self.query(ServiceProviderConfig))
|
|
1072
|
+
resource_types_response = await resources_task
|
|
1073
|
+
schemas_response = await schemas_task
|
|
1074
|
+
self.service_provider_config = await spc_task
|
|
1075
|
+
self.resource_types = resource_types_response.resources
|
|
1076
|
+
schemas = schemas_response.resources
|
|
1077
|
+
self.resource_models = self.build_resource_models(self.resource_types, schemas)
|
scim2_client/engines/werkzeug.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from contextlib import contextmanager
|
|
2
3
|
from typing import Optional
|
|
3
4
|
from typing import Union
|
|
@@ -13,6 +14,7 @@ from werkzeug.test import Client
|
|
|
13
14
|
|
|
14
15
|
from scim2_client.client import BaseSyncSCIMClient
|
|
15
16
|
from scim2_client.errors import SCIMClientError
|
|
17
|
+
from scim2_client.errors import UnexpectedContentFormat
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
@contextmanager
|
|
@@ -20,6 +22,9 @@ def handle_response_error(response):
|
|
|
20
22
|
try:
|
|
21
23
|
yield
|
|
22
24
|
|
|
25
|
+
except json.decoder.JSONDecodeError as exc:
|
|
26
|
+
raise UnexpectedContentFormat(source=response) from exc
|
|
27
|
+
|
|
23
28
|
except SCIMClientError as exc:
|
|
24
29
|
exc.source = response
|
|
25
30
|
raise exc
|
|
@@ -58,6 +63,9 @@ class TestSCIMClient(BaseSyncSCIMClient):
|
|
|
58
63
|
assert response_user.user_name == "foo"
|
|
59
64
|
"""
|
|
60
65
|
|
|
66
|
+
# avoid making Pytest believe this is a test class
|
|
67
|
+
__test__ = False
|
|
68
|
+
|
|
61
69
|
def __init__(self, app, *args, scim_prefix: str = "", **kwargs):
|
|
62
70
|
super().__init__(*args, **kwargs)
|
|
63
71
|
self.client = Client(app)
|
scim2_client/errors.py
CHANGED
|
@@ -35,7 +35,7 @@ class RequestNetworkError(SCIMRequestError):
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class RequestPayloadValidationError(SCIMRequestError):
|
|
38
|
-
"""Error raised when an invalid request payload has been passed to
|
|
38
|
+
"""Error raised when an invalid request payload has been passed to SCIMClient.
|
|
39
39
|
|
|
40
40
|
This error is raised when a :class:`pydantic.ValidationError` has been caught
|
|
41
41
|
while validating the client request payload.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: scim2-client
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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
|
|
@@ -234,7 +234,7 @@ Description-Content-Type: text/markdown
|
|
|
234
234
|
A SCIM client Python library built upon [scim2-models](https://scim2-models.readthedocs.io) ,
|
|
235
235
|
that pythonically build requests and parse responses,
|
|
236
236
|
following the [RFC7643](https://datatracker.ietf.org/doc/html/rfc7643.html) and [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644.html) specifications.
|
|
237
|
-
You can use whatever request engine you prefer to perform network requests, but scim2-
|
|
237
|
+
You can use whatever request engine you prefer to perform network requests, but scim2-client comes with [httpx](https://github.com/encode/httpx) support.
|
|
238
238
|
|
|
239
239
|
It aims to be used in SCIM client applications, or in unit tests for SCIM server applications.
|
|
240
240
|
|
|
@@ -253,21 +253,24 @@ pip install scim2-client[httpx]
|
|
|
253
253
|
|
|
254
254
|
## Usage
|
|
255
255
|
|
|
256
|
-
Check the [tutorial](https://scim2-client.readthedocs.io/en/latest/tutorial.html)
|
|
256
|
+
Check the [tutorial](https://scim2-client.readthedocs.io/en/latest/tutorial.html)
|
|
257
|
+
and the [reference](https://scim2-client.readthedocs.io/en/latest/reference.html) for more details.
|
|
257
258
|
|
|
258
259
|
Here is an example of usage:
|
|
259
260
|
|
|
260
261
|
```python
|
|
261
262
|
import datetime
|
|
262
263
|
from httpx import Client
|
|
263
|
-
from scim2_models import
|
|
264
|
+
from scim2_models import Error
|
|
264
265
|
from scim2_client.engines.httpx import SyncSCIMClient
|
|
265
266
|
|
|
266
267
|
client = Client(base_url="https://auth.example/scim/v2", headers={"Authorization": "Bearer foobar"})
|
|
267
|
-
scim = SyncSCIMClient(client
|
|
268
|
+
scim = SyncSCIMClient(client)
|
|
269
|
+
scim.discover()
|
|
270
|
+
User = scim.get_resource_model("User")
|
|
268
271
|
|
|
269
272
|
# Query resources
|
|
270
|
-
user = scim.query(User
|
|
273
|
+
user = scim.query(User, "2819c223-7f76-453a-919d-413861904646")
|
|
271
274
|
assert user.user_name == "bjensen@example.com"
|
|
272
275
|
assert user.meta.last_updated == datetime.datetime(
|
|
273
276
|
2024, 4, 13, 12, 0, 0, tzinfo=datetime.timezone.utc
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
scim2_client/__init__.py,sha256=l0pyBLiTpFA68ao98PqQLT_Xx0mw8BHumwrIHYCWa_M,845
|
|
2
|
+
scim2_client/client.py,sha256=oS5ZVvQ-z9wfY-xjGTXxVjZZ9tAb8BvEAXH_eBdhBGE,44077
|
|
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=vWBITiF0FMEy1NXBBDCc5Y49CzQuztPURh2yurFyyMo,10096
|
|
8
|
+
scim2_client-0.4.0.dist-info/METADATA,sha256=_fzzrIeYimg6g6sTq3ltewSWJFUHqootg-zsxJ5el-c,16961
|
|
9
|
+
scim2_client-0.4.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
10
|
+
scim2_client-0.4.0.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
11
|
+
scim2_client-0.4.0.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
scim2_client/__init__.py,sha256=KpNDJW9e2WseqSsQ6FltkQzemHM52NACfpJTd3eSIuY,853
|
|
2
|
-
scim2_client/client.py,sha256=C45mWwWlesXv7zdANmDf9psz3JczV02cI7Pc6becg3M,39742
|
|
3
|
-
scim2_client/errors.py,sha256=mxjUvarjvX3797w2NzdLqc6zxCQZlOga8cYABWpUAXM,4401
|
|
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=s7BV8CEbLewnkF0EAsF-3J3khOhaT7CkEN9v230ZA8w,9838
|
|
8
|
-
scim2_client-0.3.2.dist-info/METADATA,sha256=Zu2tIFT443yoDOKu2AjAtBSWxRB1x9WXqpf1GM0Af0w,16997
|
|
9
|
-
scim2_client-0.3.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
10
|
-
scim2_client-0.3.2.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
11
|
-
scim2_client-0.3.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|