scim2-client 0.3.3__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 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
- "BaseSCIMClient",
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 BaseSCIMClient:
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 tuple of :class:`~scim2_models.Resource` types expected to be handled by the SCIM client.
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[tuple[type[Resource]]] = None,
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
- set(resource_models or []) | {ResourceType, Schema, ServiceProviderConfig}
154
- )
155
- self.check_request_payload = True
156
- self.check_response_payload = True
157
- self.raise_scim_errors = True
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 resource_model not in self.resource_models:
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
- try:
176
- first_bracket_index = resource_model.__name__.index("[")
177
- root_name = resource_model.__name__[:first_bracket_index]
178
- except ValueError:
179
- root_name = resource_model.__name__
180
- return f"/{root_name}s"
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
- class BaseSyncSCIMClient(BaseSCIMClient):
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
- ] = BaseSCIMClient.CREATION_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_request_payload`.
499
- :param check_response_payload: If set, overwrites :paramref:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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
- ] = BaseSCIMClient.QUERY_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_request_payload`.
551
- :param check_response_payload: If set, overwrites :paramref:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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
- ] = BaseSCIMClient.SEARCH_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_request_payload`.
620
- :param check_response_payload: If set, overwrites :paramref:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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
- ] = BaseSCIMClient.DELETION_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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
- ] = BaseSCIMClient.REPLACEMENT_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_request_payload`.
705
- :param check_response_payload: If set, overwrites :paramref:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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(BaseSCIMClient):
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
- ] = BaseSCIMClient.CREATION_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_request_payload`.
756
- :param check_response_payload: If set, overwrites :paramref:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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
- ] = BaseSCIMClient.QUERY_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_request_payload`.
808
- :param check_response_payload: If set, overwrites :paramref:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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
- ] = BaseSCIMClient.SEARCH_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_request_payload`.
877
- :param check_response_payload: If set, overwrites :paramref:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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
- ] = BaseSCIMClient.DELETION_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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
- ] = BaseSCIMClient.REPLACEMENT_RESPONSE_STATUS_CODES,
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:`BaseSCIMClient.check_request_payload`.
962
- :param check_response_payload: If set, overwrites :paramref:`BaseSCIMClient.check_response_payload`.
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:`BaseSCIMClient.raise_scim_errors`.
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)
@@ -63,6 +63,9 @@ class TestSCIMClient(BaseSyncSCIMClient):
63
63
  assert response_user.user_name == "foo"
64
64
  """
65
65
 
66
+ # avoid making Pytest believe this is a test class
67
+ __test__ = False
68
+
66
69
  def __init__(self, app, *args, scim_prefix: str = "", **kwargs):
67
70
  super().__init__(*args, **kwargs)
68
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 BaseSCIMClient.
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.3
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-models comes with [httpx](https://github.com/encode/httpx) support.
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) and the [reference](https://scim2-client.readthedocs.io/en/latest/reference.html) for more details.
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 User, EnterpriseUser, Group, Error
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, resource_types=(User[EnterpriseUser], Group))
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[EnterpriseUser], "2819c223-7f76-453a-919d-413861904646")
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=fvlKjVCRoXwo7xddLIkb4KHDRBhigNfchHAwIShemlc,10019
8
- scim2_client-0.3.3.dist-info/METADATA,sha256=vfOPfVn_DRm_rYaozGBMrzrkdr4csJ4jbyLL2fjzS8I,16997
9
- scim2_client-0.3.3.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
10
- scim2_client-0.3.3.dist-info/licenses/LICENSE.md,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
11
- scim2_client-0.3.3.dist-info/RECORD,,