scim2-client 0.1.0__py3-none-any.whl → 0.1.2__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 CHANGED
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import json.decoder
3
+ from typing import Dict
3
4
  from typing import List
4
5
  from typing import Optional
5
6
  from typing import Tuple
@@ -10,6 +11,7 @@ from httpx import Client
10
11
  from httpx import Response
11
12
  from pydantic import ValidationError
12
13
  from scim2_models import AnyResource
14
+ from scim2_models import Context
13
15
  from scim2_models import Error
14
16
  from scim2_models import ListResponse
15
17
  from scim2_models import PatchOp
@@ -28,6 +30,71 @@ BASE_HEADERS = {
28
30
  class SCIMClient:
29
31
  """An object that perform SCIM requests and validate responses."""
30
32
 
33
+ CREATION_RESPONSE_STATUS_CODES: List[int] = [
34
+ 201,
35
+ 409,
36
+ 307,
37
+ 308,
38
+ 400,
39
+ 401,
40
+ 403,
41
+ 404,
42
+ 500,
43
+ ]
44
+ """Resource creation HTTP codes defined at :rfc:`RFC7644 §3.3
45
+ <7644#section-3.3>` and :rfc:`RFC7644 §3.12 <7644#section-3.12>`"""
46
+
47
+ QUERY_RESPONSE_STATUS_CODES: List[int] = [200, 400, 307, 308, 401, 403, 404, 500]
48
+ """Resource querying HTTP codes defined at :rfc:`RFC7644 §3.4.2
49
+ <7644#section-3.4.2>` and :rfc:`RFC7644 §3.12 <7644#section-3.12>`"""
50
+
51
+ SEARCH_RESPONSE_STATUS_CODES: List[int] = [
52
+ 200,
53
+ 307,
54
+ 308,
55
+ 400,
56
+ 401,
57
+ 403,
58
+ 404,
59
+ 409,
60
+ 413,
61
+ 500,
62
+ 501,
63
+ ]
64
+ """Resource querying HTTP codes defined at :rfc:`RFC7644 §3.4.3
65
+ <7644#section-3.4.3>` and :rfc:`RFC7644 §3.12 <7644#section-3.12>`"""
66
+
67
+ DELETION_RESPONSE_STATUS_CODES: List[int] = [
68
+ 204,
69
+ 307,
70
+ 308,
71
+ 400,
72
+ 401,
73
+ 403,
74
+ 404,
75
+ 412,
76
+ 500,
77
+ 501,
78
+ ]
79
+ """Resource deletion HTTP codes defined at :rfc:`RFC7644 §3.6
80
+ <7644#section-3.6>` and :rfc:`RFC7644 §3.12 <7644#section-3.12>`"""
81
+
82
+ REPLACEMENT_RESPONSE_STATUS_CODES: List[int] = [
83
+ 200,
84
+ 307,
85
+ 308,
86
+ 400,
87
+ 401,
88
+ 403,
89
+ 404,
90
+ 409,
91
+ 412,
92
+ 500,
93
+ 501,
94
+ ]
95
+ """Resource querying HTTP codes defined at :rfc:`RFC7644 §3.4.2
96
+ <7644#section-3.4.2>` and :rfc:`RFC7644 §3.12 <7644#section-3.12>`"""
97
+
31
98
  def __init__(self, client: Client, resource_types: Optional[Tuple[Type]] = None):
32
99
  self.client = client
33
100
  self.resource_types = resource_types or ()
@@ -44,8 +111,9 @@ class SCIMClient:
44
111
  response: Response,
45
112
  expected_status_codes: List[int],
46
113
  expected_type: Optional[Type] = None,
114
+ scim_ctx: Optional[Context] = None,
47
115
  ):
48
- if response.status_code not in expected_status_codes:
116
+ if expected_status_codes and response.status_code not in expected_status_codes:
49
117
  raise UnexpectedStatusCode(response)
50
118
 
51
119
  # Interoperability considerations: The "application/scim+json" media
@@ -62,7 +130,8 @@ class SCIMClient:
62
130
  # the errors in the body of the response in a JSON format
63
131
  # https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.12
64
132
 
65
- if response.status_code in (204, 205):
133
+ no_content_status_codes = [204, 205]
134
+ if response.status_code in no_content_status_codes:
66
135
  response_payload = None
67
136
 
68
137
  else:
@@ -77,14 +146,31 @@ class SCIMClient:
77
146
  pass
78
147
 
79
148
  if expected_type:
80
- return expected_type.model_validate(response_payload)
149
+ try:
150
+ return expected_type.model_validate(response_payload, scim_ctx=scim_ctx)
151
+ except ValidationError as exc:
152
+ exc.response_payload = response_payload
153
+ raise exc
154
+
81
155
  return response_payload
82
156
 
83
- def create(self, resource: AnyResource, **kwargs) -> Union[AnyResource, Error]:
157
+ def create(
158
+ self,
159
+ resource: Union[AnyResource, Dict],
160
+ check_request_payload: bool = True,
161
+ check_response_payload: bool = True,
162
+ check_status_code: bool = True,
163
+ **kwargs,
164
+ ) -> Union[AnyResource, Error, Dict]:
84
165
  """Perform a POST request to create, as defined in :rfc:`RFC7644 §3.3
85
166
  <7644#section-3.3>`.
86
167
 
87
168
  :param resource: The resource to create
169
+ :param check_request_payload: If :data:`False`,
170
+ :code:`resource` is expected to be a dict that will be passed as-is in the request.
171
+ :param check_response_payload: Whether to validate that the response payload is valid.
172
+ If set, the raw payload will be returned.
173
+ :param check_status_code: Whether to validate that the response status code is valid.
88
174
  :param kwargs: Additional parameters passed to the underlying HTTP request
89
175
  library.
90
176
 
@@ -93,35 +179,36 @@ class SCIMClient:
93
179
  - The created object as returned by the server in case of success.
94
180
  """
95
181
 
96
- self.check_resource_type(resource.__class__)
97
- url = self.resource_endpoint(resource.__class__)
98
- dump = resource.model_dump(exclude_none=True, by_alias=True, mode="json")
99
- response = self.client.post(url, json=dump, **kwargs)
100
-
101
- expected_status_codes = [
102
- # Resource creation HTTP codes defined at:
103
- # https://datatracker.ietf.org/doc/html/rfc7644#section-3.3
104
- 201,
105
- 409,
106
- # Default HTTP codes defined at:
107
- # https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.12
108
- 307,
109
- 308,
110
- 400,
111
- 401,
112
- 403,
113
- 404,
114
- 500,
115
- ]
116
- return self.check_response(response, expected_status_codes, resource.__class__)
182
+ if not check_request_payload:
183
+ payload = resource
184
+ url = kwargs.pop("url", None)
185
+
186
+ else:
187
+ self.check_resource_type(resource.__class__)
188
+ url = kwargs.pop("url", self.resource_endpoint(resource.__class__))
189
+ payload = resource.model_dump(scim_ctx=Context.RESOURCE_CREATION_REQUEST)
190
+
191
+ response = self.client.post(url, json=payload, **kwargs)
192
+
193
+ return self.check_response(
194
+ response,
195
+ self.CREATION_RESPONSE_STATUS_CODES if check_status_code else None,
196
+ resource.__class__
197
+ if check_request_payload and check_response_payload
198
+ else None,
199
+ scim_ctx=Context.RESOURCE_CREATION_RESPONSE,
200
+ )
117
201
 
118
202
  def query(
119
203
  self,
120
204
  resource_type: Type,
121
205
  id: Optional[str] = None,
122
- search_request: Optional[SearchRequest] = None,
206
+ search_request: Optional[Union[SearchRequest, Dict]] = None,
207
+ check_request_payload: bool = True,
208
+ check_response_payload: bool = True,
209
+ check_status_code: bool = True,
123
210
  **kwargs,
124
- ) -> Union[AnyResource, ListResponse[AnyResource], Error]:
211
+ ) -> Union[AnyResource, ListResponse[AnyResource], Error, Dict]:
125
212
  """Perform a GET request to read resources, as defined in :rfc:`RFC7644
126
213
  §3.4.2 <7644#section-3.4.2>`.
127
214
 
@@ -131,6 +218,11 @@ class SCIMClient:
131
218
  :param resource_type: A :class:`~scim2_models.Resource` subtype or :data:`None`
132
219
  :param id: The SCIM id of an object to get, or :data:`None`
133
220
  :param search_request: An object detailing the search query parameters.
221
+ :param check_request_payload: If :data:`False`,
222
+ :code:`search_request` is expected to be a dict that will be passed as-is in the request.
223
+ :param check_response_payload: Whether to validate that the response payload is valid.
224
+ If set, the raw payload will be returned.
225
+ :param check_status_code: Whether to validate that the response status code is valid.
134
226
  :param kwargs: Additional parameters passed to the underlying HTTP request library.
135
227
 
136
228
  :return:
@@ -140,13 +232,18 @@ class SCIMClient:
140
232
  """
141
233
 
142
234
  self.check_resource_type(resource_type)
143
- payload = (
144
- search_request.model_dump(
145
- by_alias=True, exclude_none=True, exclude_unset=True, mode="json"
235
+ if not check_request_payload:
236
+ payload = search_request
237
+
238
+ else:
239
+ payload = (
240
+ search_request.model_dump(
241
+ exclude_unset=True,
242
+ scim_ctx=Context.RESOURCE_QUERY_REQUEST,
243
+ )
244
+ if search_request
245
+ else None
146
246
  )
147
- if search_request
148
- else None
149
- )
150
247
 
151
248
  if not id:
152
249
  expected_type = ListResponse[resource_type]
@@ -156,32 +253,31 @@ class SCIMClient:
156
253
  expected_type = resource_type
157
254
  url = self.resource_endpoint(resource_type) + f"/{id}"
158
255
 
159
- expected_status_codes = [
160
- # Resource querying HTTP codes defined at:
161
- # https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.2
162
- 200,
163
- 400,
164
- # Default HTTP codes defined at:
165
- # https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.12
166
- 307,
167
- 308,
168
- 401,
169
- 403,
170
- 404,
171
- 500,
172
- ]
173
256
  response = self.client.get(url, params=payload, **kwargs)
174
- return self.check_response(response, expected_status_codes, expected_type)
257
+ return self.check_response(
258
+ response,
259
+ self.QUERY_RESPONSE_STATUS_CODES if check_status_code else None,
260
+ expected_type if check_response_payload else None,
261
+ scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
262
+ )
175
263
 
176
264
  def query_all(
177
265
  self,
178
266
  search_request: Optional[SearchRequest] = None,
267
+ check_request_payload: bool = True,
268
+ check_response_payload: bool = True,
269
+ check_status_code: bool = True,
179
270
  **kwargs,
180
- ) -> Union[AnyResource, ListResponse[AnyResource], Error]:
271
+ ) -> Union[AnyResource, ListResponse[AnyResource], Error, Dict]:
181
272
  """Perform a GET request to read all available resources, as defined in
182
273
  :rfc:`RFC7644 §3.4.2.1 <7644#section-3.4.2.1>`.
183
274
 
184
275
  :param search_request: An object detailing the search query parameters.
276
+ :param check_request_payload: If :data:`False`,
277
+ :code:`search_request` is expected to be a dict that will be passed as-is in the request.
278
+ :param check_response_payload: Whether to validate that the response payload is valid.
279
+ If set, the raw payload will be returned.
280
+ :param check_status_code: Whether to validate that the response status code is valid.
185
281
  :param kwargs: Additional parameters passed to the underlying
186
282
  HTTP request library.
187
283
 
@@ -194,46 +290,48 @@ class SCIMClient:
194
290
  # server SHALL be included, subject to filtering.
195
291
  # https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.4.2.1
196
292
 
197
- payload = (
198
- search_request.model_dump(
199
- by_alias=True, exclude_none=True, exclude_unset=True, mode="json"
293
+ if not check_request_payload:
294
+ payload = search_request
295
+
296
+ else:
297
+ payload = (
298
+ search_request.model_dump(
299
+ exclude_unset=True, scim_ctx=Context.RESOURCE_QUERY_REQUEST
300
+ )
301
+ if search_request
302
+ else None
200
303
  )
201
- if search_request
202
- else None
203
- )
204
- response = self.client.get("/", params=payload)
205
304
 
206
- expected_status_codes = [
207
- # Resource querying HTTP codes defined at:
208
- # https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.2
209
- 200,
210
- 400,
211
- # Default HTTP codes defined at:
212
- # https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.12
213
- 307,
214
- 308,
215
- 401,
216
- 403,
217
- 404,
218
- 500,
219
- 501,
220
- ]
305
+ response = self.client.get("/", params=payload)
221
306
 
222
307
  return self.check_response(
223
- response, expected_status_codes, ListResponse[Union[self.resource_types]]
308
+ response,
309
+ self.QUERY_RESPONSE_STATUS_CODES if check_status_code else None,
310
+ ListResponse[Union[self.resource_types]]
311
+ if check_response_payload
312
+ else None,
313
+ scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
224
314
  )
225
315
 
226
316
  def search(
227
317
  self,
228
318
  search_request: Optional[SearchRequest] = None,
319
+ check_request_payload: bool = True,
320
+ check_response_payload: bool = True,
321
+ check_status_code: bool = True,
229
322
  **kwargs,
230
- ) -> Union[AnyResource, ListResponse[AnyResource], Error]:
323
+ ) -> Union[AnyResource, ListResponse[AnyResource], Error, Dict]:
231
324
  """Perform a POST search request to read all available resources, as
232
325
  defined in :rfc:`RFC7644 §3.4.3 <7644#section-3.4.3>`.
233
326
 
234
327
  :param resource_types: Resource type or union of types expected
235
328
  to be read from the response.
236
329
  :param search_request: An object detailing the search query parameters.
330
+ :param check_request_payload: If :data:`False`,
331
+ :code:`search_request` is expected to be a dict that will be passed as-is in the request.
332
+ :param check_response_payload: Whether to validate that the response payload is valid.
333
+ If set, the raw payload will be returned.
334
+ :param check_status_code: Whether to validate that the response status code is valid.
237
335
  :param kwargs: Additional parameters passed to the underlying
238
336
  HTTP request library.
239
337
 
@@ -242,40 +340,36 @@ class SCIMClient:
242
340
  - A :class:`~scim2_models.ListResponse[resource_type]` object in case of success.
243
341
  """
244
342
 
245
- payload = (
246
- search_request.model_dump(
247
- by_alias=True, exclude_none=True, exclude_unset=True, mode="json"
343
+ if not check_request_payload:
344
+ payload = search_request
345
+
346
+ else:
347
+ payload = (
348
+ search_request.model_dump(
349
+ exclude_unset=True, scim_ctx=Context.RESOURCE_QUERY_RESPONSE
350
+ )
351
+ if search_request
352
+ else None
248
353
  )
249
- if search_request
250
- else None
251
- )
354
+
252
355
  response = self.client.post("/.search", params=payload)
253
356
 
254
- expected_status_codes = [
255
- # Resource querying HTTP codes defined at:
256
- # https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.3
257
- 200,
258
- # Default HTTP codes defined at:
259
- # https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.12
260
- 307,
261
- 308,
262
- 400,
263
- 401,
264
- 403,
265
- 404,
266
- 409,
267
- 413,
268
- 500,
269
- 501,
270
- ]
271
357
  return self.check_response(
272
- response, expected_status_codes, ListResponse[Union[self.resource_types]]
358
+ response,
359
+ self.SEARCH_RESPONSE_STATUS_CODES if check_status_code else None,
360
+ ListResponse[Union[self.resource_types]]
361
+ if check_response_payload
362
+ else None,
363
+ scim_ctx=Context.RESOURCE_QUERY_RESPONSE,
273
364
  )
274
365
 
275
- def delete(self, resource_type: Type, id: str, **kwargs) -> Optional[Error]:
366
+ def delete(
367
+ self, resource_type: Type, id: str, check_status_code: bool = True, **kwargs
368
+ ) -> Optional[Union[Error, Dict]]:
276
369
  """Perform a DELETE request to create, as defined in :rfc:`RFC7644 §3.6
277
370
  <7644#section-3.6>`.
278
371
 
372
+ :param check_status_code: Whether to validate that the response status code is valid.
279
373
  :param kwargs: Additional parameters passed to the underlying
280
374
  HTTP request library.
281
375
 
@@ -288,29 +382,27 @@ class SCIMClient:
288
382
  url = self.resource_endpoint(resource_type) + f"/{id}"
289
383
  response = self.client.delete(url, **kwargs)
290
384
 
291
- expected_status_codes = [
292
- # Resource deletion HTTP codes defined at:
293
- # https://datatracker.ietf.org/doc/html/rfc7644#section-3.6
294
- 204,
295
- # Default HTTP codes defined at:
296
- # https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.12
297
- 307,
298
- 308,
299
- 400,
300
- 401,
301
- 403,
302
- 404,
303
- 412,
304
- 500,
305
- 501,
306
- ]
307
- return self.check_response(response, expected_status_codes)
308
-
309
- def replace(self, resource: AnyResource, **kwargs) -> Union[AnyResource, Error]:
385
+ return self.check_response(
386
+ response, self.DELETION_RESPONSE_STATUS_CODES if check_status_code else None
387
+ )
388
+
389
+ def replace(
390
+ self,
391
+ resource: Union[AnyResource, Dict],
392
+ check_request_payload: bool = True,
393
+ check_response_payload: bool = True,
394
+ check_status_code: bool = True,
395
+ **kwargs,
396
+ ) -> Union[AnyResource, Error, Dict]:
310
397
  """Perform a PUT request to replace a resource, as defined in
311
398
  :rfc:`RFC7644 §3.5.1 <7644#section-3.5.1>`.
312
399
 
313
400
  :param resource: The new state of the resource to replace.
401
+ :param check_request_payload: If :data:`False`,
402
+ :code:`resource` is expected to be a dict that will be passed as-is in the request.
403
+ :param check_response_payload: Whether to validate that the response payload is valid.
404
+ If set, the raw payload will be returned.
405
+ :param check_status_code: Whether to validate that the response status code is valid.
314
406
  :param kwargs: Additional parameters passed to the underlying
315
407
  HTTP request library.
316
408
 
@@ -319,34 +411,32 @@ class SCIMClient:
319
411
  - The updated object as returned by the server in case of success.
320
412
  """
321
413
 
322
- self.check_resource_type(resource.__class__)
323
- if not resource.id:
324
- raise Exception("Resource must have an id")
325
-
326
- dump = resource.model_dump(exclude_none=True, by_alias=True, mode="json")
327
- url = self.resource_endpoint(resource.__class__) + f"/{resource.id}"
328
- response = self.client.put(url, json=dump, **kwargs)
329
-
330
- expected_status_codes = [
331
- # Resource querying HTTP codes defined at:
332
- # https://datatracker.ietf.org/doc/html/rfc7644#section-3.4.2
333
- 200,
334
- # Default HTTP codes defined at:
335
- # https://datatracker.ietf.org/doc/html/rfc7644.html#section-3.12
336
- 307,
337
- 308,
338
- 400,
339
- 401,
340
- 403,
341
- 404,
342
- 409,
343
- 412,
344
- 500,
345
- 501,
346
- ]
347
- return self.check_response(response, expected_status_codes, resource.__class__)
414
+ if not check_request_payload:
415
+ payload = resource
416
+ url = kwargs.pop("url")
417
+
418
+ else:
419
+ self.check_resource_type(resource.__class__)
420
+ if not resource.id:
421
+ raise Exception("Resource must have an id")
422
+
423
+ payload = resource.model_dump(scim_ctx=Context.RESOURCE_REPLACEMENT_REQUEST)
424
+ url = kwargs.pop(
425
+ "url", self.resource_endpoint(resource.__class__) + f"/{resource.id}"
426
+ )
427
+
428
+ response = self.client.put(url, json=payload, **kwargs)
429
+
430
+ return self.check_response(
431
+ response,
432
+ self.REPLACEMENT_RESPONSE_STATUS_CODES if check_status_code else None,
433
+ resource.__class__
434
+ if check_request_payload and check_response_payload
435
+ else None,
436
+ scim_ctx=Context.RESOURCE_REPLACEMENT_RESPONSE,
437
+ )
348
438
 
349
439
  def modify(
350
- self, resource: AnyResource, op: PatchOp, **kwargs
351
- ) -> Optional[AnyResource]:
440
+ self, resource: Union[AnyResource, Dict], op: PatchOp, **kwargs
441
+ ) -> Optional[Union[AnyResource, Dict]]:
352
442
  raise NotImplementedError()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: scim2-client
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Pythonically build SCIM requests and parse SCIM responses
5
5
  License: MIT
6
6
  Keywords: scim,scim2,provisioning,httpx,api
@@ -20,12 +20,14 @@ Classifier: Programming Language :: Python :: 3.11
20
20
  Classifier: Programming Language :: Python :: 3.12
21
21
  Classifier: Programming Language :: Python :: Implementation :: CPython
22
22
  Requires-Dist: httpx (>=0.27.0,<0.28.0)
23
- Requires-Dist: scim2-models (>=0.1.0,<0.2.0)
23
+ Requires-Dist: scim2-models (>=0.1.1,<0.2.0)
24
24
  Description-Content-Type: text/markdown
25
25
 
26
26
  # scim2-client
27
27
 
28
- A SCIM client library built upon [scim2-models](https://scim2-models.readthedocs.io), that pythonically build requests and parse responses, following the [RFC7643](https://datatracker.ietf.org/doc/html/rfc7643.html) and [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644.html) specifications.
28
+ A SCIM client Python library built upon [scim2-models](https://scim2-models.readthedocs.io) and [httpx](https://github.com/encode/httpx),
29
+ that pythonically build requests and parse responses,
30
+ following the [RFC7643](https://datatracker.ietf.org/doc/html/rfc7643.html) and [RFC7644](https://datatracker.ietf.org/doc/html/rfc7644.html) specifications.
29
31
  ## Installation
30
32
 
31
33
  ```shell
@@ -40,7 +42,7 @@ Here is an example of usage:
40
42
 
41
43
  ```python
42
44
  import datetime
43
- from httpx impont Client
45
+ from httpx import Client
44
46
  from scim2_models import User, EnterpriseUserUser, Group, Error
45
47
  from scim2_client import SCIMClient
46
48
 
@@ -0,0 +1,6 @@
1
+ scim2_client/__init__.py,sha256=2UNsl6HNtVUv5LVcnmLaCHyT4SqAUUFOIWW2r5XGv6A,338
2
+ scim2_client/client.py,sha256=_ZjMd6_Kh5SYEcstm5OkMIu0z-N93R4SWdG0lGd4u7s,16538
3
+ scim2_client/errors.py,sha256=uOOAwsD8rDrC8BQbwPid051YyWtexTcS8b7i6QC6-CM,1175
4
+ scim2_client-0.1.2.dist-info/METADATA,sha256=4h7QD3jpYRhSQCBmLX9AFhHJ9R68_ObP45V9_kV51LI,2654
5
+ scim2_client-0.1.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
6
+ scim2_client-0.1.2.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- scim2_client/__init__.py,sha256=2UNsl6HNtVUv5LVcnmLaCHyT4SqAUUFOIWW2r5XGv6A,338
2
- scim2_client/client.py,sha256=hLrOaXIzPE_4ZlJKAJNqWOEnFAVEo7ctPuns9qSOJ1E,12635
3
- scim2_client/errors.py,sha256=uOOAwsD8rDrC8BQbwPid051YyWtexTcS8b7i6QC6-CM,1175
4
- scim2_client-0.1.0.dist-info/METADATA,sha256=HY_uiLBOOMueWBN_bMbi2rq_G6q6iKfJNoCauSHeavQ,2602
5
- scim2_client-0.1.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
6
- scim2_client-0.1.0.dist-info/RECORD,,