scim2-cli 0.2.0__tar.gz → 0.2.2__tar.gz

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.
Files changed (38) hide show
  1. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.pre-commit-config.yaml +1 -1
  2. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/PKG-INFO +2 -2
  3. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/changelog.rst +16 -0
  4. scim2_cli-0.2.2/doc/tutorial.rst +250 -0
  5. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/pyproject.toml +2 -2
  6. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/__init__.py +25 -9
  7. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/create.py +6 -13
  8. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/delete.py +3 -9
  9. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/query.py +3 -9
  10. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/replace.py +3 -9
  11. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/search.py +3 -10
  12. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/test.py +21 -1
  13. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/utils.py +10 -0
  14. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_cli.py +163 -14
  15. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_test.py +9 -2
  16. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/uv.lock +33 -33
  17. scim2_cli-0.2.0/doc/tutorial.rst +0 -47
  18. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.github/FUNDING.yml +0 -0
  19. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.github/workflows/release.yml +0 -0
  20. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.github/workflows/tests.yaml +0 -0
  21. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.gitignore +0 -0
  22. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/.readthedocs.yml +0 -0
  23. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/LICENSE.md +0 -0
  24. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/README.md +0 -0
  25. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/__init__.py +0 -0
  26. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/conf.py +0 -0
  27. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/contributing.rst +0 -0
  28. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/index.rst +0 -0
  29. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/doc/reference.rst +0 -0
  30. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/scim2_cli/py.typed +0 -0
  31. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/__init__.py +0 -0
  32. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/conftest.py +0 -0
  33. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_create.py +0 -0
  34. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_delete.py +0 -0
  35. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_query.py +0 -0
  36. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_replace.py +0 -0
  37. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_search.py +0 -0
  38. {scim2_cli-0.2.0 → scim2_cli-0.2.2}/tests/test_utils.py +0 -0
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  repos:
3
3
  - repo: https://github.com/astral-sh/ruff-pre-commit
4
- rev: 'v0.8.1'
4
+ rev: 'v0.8.2'
5
5
  hooks:
6
6
  - id: ruff
7
7
  args: [--fix, --exit-non-zero-on-fix]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: scim2-cli
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: SCIM application development CLI
5
5
  Project-URL: documentation, https://scim2-cli.readthedocs.io
6
6
  Project-URL: repository, https://github.com/python-scim/scim2-cli
@@ -224,7 +224,7 @@ Requires-Python: >=3.10
224
224
  Requires-Dist: click>=8.1.7
225
225
  Requires-Dist: pydanclick>=0.3.0
226
226
  Requires-Dist: pygments>=2.18.0
227
- Requires-Dist: scim2-client>=0.4.2
227
+ Requires-Dist: scim2-client>=0.4.3
228
228
  Requires-Dist: scim2-tester[httpx]>=0.1.10
229
229
  Requires-Dist: sphinx-click-rst-to-ansi-formatter>=0.1.0
230
230
  Description-Content-Type: text/markdown
@@ -1,6 +1,22 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ [0.2.2] - 2024-12-06
5
+ --------------------
6
+
7
+ Added
8
+ ^^^^^
9
+ - The :ref:`test` command returns 1 in case of errors.
10
+ - Added :ref:`test` ``--dont-check-content-type`` and ``--dont-check-status-code`` options.
11
+
12
+ [0.2.1] - 2024-12-04
13
+ --------------------
14
+
15
+ Added
16
+ ^^^^^
17
+ - Display server discovery step network connections.
18
+ - Support loading server configuration objects in :class:`~scim2_models.ListResponses`.
19
+
4
20
  [0.2.0] - 2024-12-03
5
21
  --------------------
6
22
 
@@ -0,0 +1,250 @@
1
+ Tutorial
2
+ ========
3
+
4
+ The :ref:`cli` command allows you to interact with a SCIM server.
5
+
6
+ .. tip::
7
+
8
+ If you want to test scim2-cli with a real SCIM server, you can install and run `scim2-server <https://github.com/python-scim/scim2-server>`__:
9
+
10
+ .. code-block:: shell
11
+
12
+ $ pip install scim2-server
13
+ $ scim2-server
14
+
15
+ Then you have a functional SCIM server available at http://127.0.0.1:8080 that you can use to test the different commands.
16
+
17
+ Basic parameters
18
+ ----------------
19
+
20
+ In order to connect to a SCIM server you will need to pass the :option:`--url <scim --url>` parameter.
21
+ You can also pass additional headers, such as authentication ones, with :option:`--header <scim --header>`.
22
+
23
+ .. code-block:: shell
24
+
25
+ $ scim2 --url https://auth.example --header "Authorization: Bearer 12345" create user --user-name "bjensen@example.com"
26
+ {
27
+ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
28
+ "id": "2819c223-7f76-453a-919d-413861904646",
29
+ "userName": "bjensen@example.com",
30
+ "meta": {
31
+ "resourceType": "User",
32
+ "created": "2010-01-23T04:56:22Z",
33
+ "lastModified": "2011-05-13T04:42:34Z",
34
+ "version": 'W\\/"3694e05e9dff590"',
35
+ "location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
36
+ },
37
+ }
38
+
39
+ However passing those parameters each time you use the command can be annoying.
40
+ To make commands shorter, you can set those parameters once for all by using the :ref:`SCIM_CLI_URL <scim-url-scim_cli_url>` and :ref:`SCIM_CLI_HEADERS <scim-header-scim_cli_headers>` environment vars.
41
+
42
+ .. code-block:: shell
43
+
44
+ $ export SCIM_CLI_URL="https://auth.example"
45
+ $ export SCIM_CLI_HEADERS="Authorization: Bearer 12345"
46
+ $ scim2 create user --user-name "bjensen@example.com"
47
+ {
48
+ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
49
+ "id": "2819c223-7f76-453a-919d-413861904646",
50
+ "userName": "bjensen@example.com",
51
+ "meta": {
52
+ "resourceType": "User",
53
+ "created": "2010-01-23T04:56:22Z",
54
+ "lastModified": "2011-05-13T04:42:34Z",
55
+ "version": 'W\\/"3694e05e9dff590"',
56
+ "location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
57
+ },
58
+ }
59
+
60
+
61
+ Server configuration
62
+ --------------------
63
+
64
+ Before doing anything, the CLI needs to reach the server configuration to find out which features are available,
65
+ which resource schemas are available, and where they are located.
66
+
67
+ By default the CLI will automatically discover those resources on the server, before each command is run.
68
+
69
+ However you might find too time consuming to achieve all those network requests.
70
+ You can store those data locally and reuse them for future command runs thanks to the
71
+ :option:`--schemas <scim --schemas>`, :option:`--resource-types <scim --resource-types>` and :option:`--service-provider-config <scim --service-provider-config>` (and their corresponding environment vars :ref:`SCIM_CLI_SCHEMAS <scim-schemas-scim_cli_schemas>`, :ref:`SCIM_CLI_RESOURCE_TYPES <scim-resource_types-scim_cli_resource_types>` and :ref:`SCIM_CLI_SERVICE_PROVIDER_CONFIG <scim-service_provider_config-scim_cli_service_provider_config>`)
72
+
73
+ .. code-block:: shell
74
+ :caption: Save the configuration resources
75
+
76
+ $ scim query schema > /tmp/schemas.json
77
+ $ scim query resourcetype > /tmp/resource-types.json
78
+ $ scim query serviceproviderconfig > /tmp/service-provider-config.json
79
+
80
+ .. code-block:: shell
81
+ :caption: Load the cached resources
82
+
83
+ $ export SCIM_SCHEMAS=/tmp/schemas.json
84
+ $ export SCIM_RESOURCE_TYPES=/tmp/resource-types.json
85
+ $ export SCIM_SERVICE_PROVIDER_CONFIG=/tmp/service-provider-config.json
86
+ $ scim2 query ...
87
+
88
+ Query and search resources
89
+ --------------------------
90
+
91
+ The :ref:`query` and :ref:`search` commands can be used to look for resources.
92
+ :ref:`query` performs ag `GET` request on the resources endpoint, while :ref:`search` performs a `POST` request on the ``/.search`` endpoint.
93
+ Both commands take similar options such as :option:`--count <scim-query.--count>` or :option:`--attributes <scim-query.--attributes>`.
94
+ An exhaustive list of options can be found on the :doc:`reference`.
95
+ :ref:`query` can also take a :option:`RESOURCE_TYPE <scim-query.RESOURCE_TYPE>` and a :option:`ID <scim-query.ID>` parameters.
96
+
97
+ - If none are set, all the resources of the server are queried.
98
+
99
+ .. code-block:: console
100
+ :caption: Querying all the resources from the server.
101
+
102
+ $ scim query
103
+ {
104
+ "schemas": [
105
+ "urn:ietf:params:scim:api:messages:2.0:ListResponse"
106
+ ],
107
+ "totalResults": xx,
108
+ "startIndex": 1,
109
+ "itemsPerPage": 50,
110
+ "Resources": [...]
111
+ }
112
+ - If :option:`RESOURCE_TYPE <scim-query.RESOURCE_TYPE>` is set and :option:`ID <scim-query.ID>` is unset, all the resource of the kind passed in parameter are returned.
113
+
114
+ .. code-block:: console
115
+ :caption: Querying all the users from the server.
116
+
117
+ $ scim query user
118
+ {
119
+ "schemas": [
120
+ "urn:ietf:params:scim:api:messages:2.0:ListResponse"
121
+ ],
122
+ "totalResults": xx,
123
+ "startIndex": 1,
124
+ "itemsPerPage": 50,
125
+ "Resources": [...]
126
+ }
127
+
128
+ - If both options are set, the very resource with the ID are returned.
129
+
130
+ .. code-block:: console
131
+ :caption: Querying the user with the ID `38b044dd95624c4186f5614fca30305d`
132
+
133
+ $ scim query user 38b044dd95624c4186f5614fca30305d
134
+ {
135
+ "schemas": [
136
+ "urn:ietf:params:scim:schemas:core:2.0:User",
137
+ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
138
+ ],
139
+ "id": "38b044dd95624c4186f5614fca30305d",
140
+ "meta": {
141
+ "resourceType": "User",
142
+ "created": "2024-12-05T16:08:51.896646Z",
143
+ "lastModified": "2024-12-05T16:08:51.896646Z",
144
+ "location": "http://scim.example/v2/Users/38b044dd95624c4186f5614fca30305d",
145
+ "version": "W/\"637b1ce03c010cd55fe45b6f7be2247b5159b135\""
146
+ },
147
+ "userName": "bjensen@example.com"
148
+ }
149
+ Create and replace resources
150
+ ----------------------------
151
+
152
+ The :ref:`create` and :ref:`replace` commands can be used to edit resources.
153
+
154
+ Options for those commands are dynamically generated, depending on the resource attributes available on the server.
155
+ For instance, for the :class:`~scim2_models.User` resource, you have a ``--user-name`` option.
156
+ You can have a look at the exhaustive list of options by running ``scim create user --help``.
157
+
158
+ .. code-block:: console
159
+ :caption: Creation of an user.
160
+
161
+ $ scim create user --user-name bjensen@example.com
162
+ {
163
+ "schemas": [
164
+ "urn:ietf:params:scim:schemas:core:2.0:User",
165
+ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
166
+ ],
167
+ "id": "38b044dd95624c4186f5614fca30305d",
168
+ "meta": {
169
+ "resourceType": "User",
170
+ "created": "2024-12-05T16:08:51.896646Z",
171
+ "lastModified": "2024-12-05T16:08:51.896646Z",
172
+ "location": "http://scim.example/v2/Users/38b044dd95624c4186f5614fca30305d",
173
+ "version": "W/\"637b1ce03c010cd55fe45b6f7be2247b5159b135\""
174
+ },
175
+ "userName": "bjensen@example.com"
176
+ }
177
+
178
+ Delete resources
179
+ ----------------
180
+
181
+ The :ref:`delete` command allows you to delete resources.
182
+
183
+ .. code-block:: console
184
+ :caption: Deletion of an user.
185
+
186
+ $ scim delete user 38b044dd95624c4186f5614fca30305d
187
+
188
+ Perform a SCIM compliance test
189
+ ------------------------------
190
+
191
+ The :ref:`test` command runs a series of resource creation, edition and deletions to check that your server complies with the SCIM specifications.
192
+ See the :doc:`scim2-tester documentation <scim2_tester:index>` for more details on which tests are performed.
193
+
194
+ .. code-block:: console
195
+ :caption: SCIM compliance test
196
+
197
+ $ scim test
198
+ Performing a SCIM compliance check on http://localhost:8080 ...
199
+ SUCCESS check_service_provider_config_endpoint
200
+ SUCCESS check_query_all_resource_types
201
+ SUCCESS check_query_resource_type_by_id
202
+ Successfully accessed the /ResourceTypes/User endpoint.
203
+ SUCCESS check_query_resource_type_by_id
204
+ Successfully accessed the /ResourceTypes/Group endpoint.
205
+ SUCCESS check_access_invalid_resource_typ
206
+ ...
207
+
208
+ JSON input
209
+ ----------
210
+
211
+ scim2-cli will also read input data from the standard input.
212
+ This can be used to send custom payloads to the SCIM server.
213
+
214
+ When user with :ref:`query` and :ref:`search`, the input value must be a JSON representation of a :class:`~scim2_models.SearchRequest` object:
215
+
216
+ .. code-block:: console
217
+ :caption: Search of an user by passing a custom payload.
218
+
219
+ $ echo '{"startIndex": 50, "count": 10}' | scim query user
220
+ {
221
+ "schemas": [
222
+ "urn:ietf:params:scim:api:messages:2.0:ListResponse"
223
+ ],
224
+ "totalResults": xx,
225
+ "startIndex": 50,
226
+ "itemsPerPage": 10,
227
+ "Resources": [...]
228
+ }
229
+
230
+ When used with :ref:`create` and :ref:`replace`, no subcommand is needed and the endpoint is guessed from the payload.
231
+
232
+ .. code-block:: console
233
+ :caption: Creation of an user by passing a custom payload.
234
+
235
+ $ echo '{"userName": "bjensen@example.com", "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"]}' | scim create
236
+ {
237
+ "schemas": [
238
+ "urn:ietf:params:scim:schemas:core:2.0:User",
239
+ "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
240
+ ],
241
+ "id": "38b044dd95624c4186f5614fca30305d",
242
+ "meta": {
243
+ "resourceType": "User",
244
+ "created": "2024-12-05T16:08:51.896646Z",
245
+ "lastModified": "2024-12-05T16:08:51.896646Z",
246
+ "location": "http://scim.example/v2/Users/38b044dd95624c4186f5614fca30305d",
247
+ "version": "W/\"637b1ce03c010cd55fe45b6f7be2247b5159b135\""
248
+ },
249
+ "userName": "bjensen@example.com"
250
+ }
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "scim2-cli"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "SCIM application development CLI"
9
9
  authors = [{name="Yaal Coop", email="contact@yaal.coop"}]
10
10
  license = {file = "LICENSE.md"}
@@ -27,7 +27,7 @@ classifiers = [
27
27
  requires-python = ">= 3.10"
28
28
  dependencies = [
29
29
  "click>=8.1.7",
30
- "scim2-client>=0.4.2",
30
+ "scim2-client>=0.4.3",
31
31
  "scim2-tester[httpx]>=0.1.10",
32
32
  "sphinx-click-rst-to-ansi-formatter>=0.1.0",
33
33
  "pydanclick>=0.3.0",
@@ -6,8 +6,10 @@ from typing import TypeGuard
6
6
  import click
7
7
  from httpx import Client
8
8
  from pydantic import BaseModel
9
+ from scim2_client import SCIMClientError
9
10
  from scim2_client.engines.httpx import SyncSCIMClient
10
11
  from scim2_models import Group
12
+ from scim2_models import ListResponse
11
13
  from scim2_models import Resource
12
14
  from scim2_models import ResourceType
13
15
  from scim2_models import Schema
@@ -23,6 +25,7 @@ from scim2_cli.search import search_cli
23
25
  from scim2_cli.test import test_cli
24
26
  from scim2_cli.utils import DOC_URL
25
27
  from scim2_cli.utils import HeaderType
28
+ from scim2_cli.utils import exception_to_click_error
26
29
  from scim2_cli.utils import split_headers
27
30
 
28
31
 
@@ -51,7 +54,10 @@ def load_config_files(
51
54
  ]:
52
55
  if schemas_fd:
53
56
  schemas_payload = json.load(schemas_fd)
54
- schemas_obj = [Schema.model_validate(schema) for schema in schemas_payload]
57
+ if isinstance(schemas_payload, dict):
58
+ schemas_obj = ListResponse[Schema].model_validate(schemas_payload).resources
59
+ else:
60
+ schemas_obj = [Schema.model_validate(schema) for schema in schemas_payload]
55
61
  resource_models = [Resource.from_schema(schema) for schema in schemas_obj]
56
62
 
57
63
  else:
@@ -59,9 +65,16 @@ def load_config_files(
59
65
 
60
66
  if resource_types_fd:
61
67
  resource_types_payload = json.load(resource_types_fd)
62
- resource_types = [
63
- ResourceType.model_validate(item) for item in resource_types_payload
64
- ]
68
+ if isinstance(schemas_payload, dict):
69
+ resource_types = (
70
+ ListResponse[ResourceType]
71
+ .model_validate(resource_types_payload)
72
+ .resources
73
+ )
74
+ else:
75
+ resource_types = [
76
+ ResourceType.model_validate(item) for item in resource_types_payload
77
+ ]
65
78
  else:
66
79
  resource_types = None
67
80
 
@@ -128,11 +141,14 @@ def cli(
128
141
  resource_types=resource_types_obj,
129
142
  service_provider_config=spc_obj,
130
143
  )
131
- scim_client.discover(
132
- schemas=not bool(schemas),
133
- resource_types=not bool(resource_types),
134
- service_provider_config=not bool(service_provider_config),
135
- )
144
+ try:
145
+ scim_client.discover(
146
+ schemas=not bool(schemas),
147
+ resource_types=not bool(resource_types),
148
+ service_provider_config=not bool(service_provider_config),
149
+ )
150
+ except SCIMClientError as exc:
151
+ raise exception_to_click_error(exc) from exc
136
152
 
137
153
  ctx.obj["client"] = scim_client
138
154
  ctx.obj["resource_models"] = {
@@ -1,5 +1,3 @@
1
- import sys
2
-
3
1
  import click
4
2
  from click import ClickException
5
3
  from pydanclick import from_pydantic
@@ -7,10 +5,11 @@ from scim2_client import SCIMClientError
7
5
  from scim2_models import Context
8
6
  from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
9
7
 
10
- from .utils import DOC_URL
11
- from .utils import ModelCommand
12
- from .utils import formatted_payload
13
- from .utils import unacceptable_fields
8
+ from scim2_cli.utils import DOC_URL
9
+ from scim2_cli.utils import ModelCommand
10
+ from scim2_cli.utils import exception_to_click_error
11
+ from scim2_cli.utils import formatted_payload
12
+ from scim2_cli.utils import unacceptable_fields
14
13
 
15
14
 
16
15
  def create_payload(client, payload, indent):
@@ -18,13 +17,7 @@ def create_payload(client, payload, indent):
18
17
  response = client.create(payload, raise_scim_errors=False)
19
18
 
20
19
  except SCIMClientError as scim_exc:
21
- message = str(scim_exc)
22
- if sys.version_info >= (3, 11) and hasattr(
23
- scim_exc, "__notes__"
24
- ): # pragma: no cover
25
- for note in scim_exc.__notes__:
26
- message = f"{message}\n{note}"
27
- raise ClickException(message) from scim_exc
20
+ raise exception_to_click_error(scim_exc) from scim_exc
28
21
 
29
22
  payload = formatted_payload(response.model_dump(), indent)
30
23
  click.echo(payload)
@@ -1,5 +1,3 @@
1
- import sys
2
-
3
1
  import click
4
2
  from click import ClickException
5
3
  from scim2_client import SCIMClientError
@@ -7,6 +5,8 @@ from scim2_models import Message
7
5
  from scim2_models import Resource
8
6
  from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
9
7
 
8
+ from scim2_cli.utils import exception_to_click_error
9
+
10
10
  from .utils import DOC_URL
11
11
  from .utils import formatted_payload
12
12
 
@@ -40,13 +40,7 @@ def delete_cli(ctx, resource_type, id, indent):
40
40
  response = ctx.obj["client"].delete(resource_model, id, raise_scim_errors=False)
41
41
 
42
42
  except SCIMClientError as scim_exc:
43
- message = str(scim_exc)
44
- if sys.version_info >= (3, 11) and hasattr(
45
- scim_exc, "__notes__"
46
- ): # pragma: no cover
47
- for note in scim_exc.__notes__:
48
- message = f"{message}\n{note}"
49
- raise ClickException(message) from scim_exc
43
+ raise exception_to_click_error(scim_exc) from scim_exc
50
44
 
51
45
  if response:
52
46
  payload = (
@@ -1,5 +1,3 @@
1
- import sys
2
-
3
1
  import click
4
2
  from click import ClickException
5
3
  from scim2_client import SCIMClientError
@@ -9,6 +7,8 @@ from scim2_models import SearchRequest
9
7
  from scim2_models import ServiceProviderConfig
10
8
  from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
11
9
 
10
+ from scim2_cli.utils import exception_to_click_error
11
+
12
12
  from .utils import DOC_URL
13
13
  from .utils import formatted_payload
14
14
 
@@ -118,13 +118,7 @@ def query_cli(
118
118
  )
119
119
 
120
120
  except SCIMClientError as scim_exc:
121
- message = str(scim_exc)
122
- if sys.version_info >= (3, 11) and hasattr(
123
- scim_exc, "__notes__"
124
- ): # pragma: no cover
125
- for note in scim_exc.__notes__:
126
- message = f"{message}\n{note}"
127
- raise ClickException(message) from scim_exc
121
+ raise exception_to_click_error(scim_exc) from scim_exc
128
122
 
129
123
  payload = formatted_payload(response.model_dump(), indent)
130
124
  click.echo(payload)
@@ -1,5 +1,3 @@
1
- import sys
2
-
3
1
  import click
4
2
  from click import ClickException
5
3
  from pydanclick import from_pydantic
@@ -7,6 +5,8 @@ from scim2_client import SCIMClientError
7
5
  from scim2_models import Context
8
6
  from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
9
7
 
8
+ from scim2_cli.utils import exception_to_click_error
9
+
10
10
  from .utils import DOC_URL
11
11
  from .utils import ModelCommand
12
12
  from .utils import formatted_payload
@@ -18,13 +18,7 @@ def replace_payload(client, payload, indent):
18
18
  response = client.replace(payload, raise_scim_errors=False)
19
19
 
20
20
  except SCIMClientError as scim_exc:
21
- message = str(scim_exc)
22
- if sys.version_info >= (3, 11) and hasattr(
23
- scim_exc, "__notes__"
24
- ): # pragma: no cover
25
- for note in scim_exc.__notes__:
26
- message = f"{message}\n{note}"
27
- raise ClickException(message) from scim_exc
21
+ raise exception_to_click_error(scim_exc) from scim_exc
28
22
 
29
23
  payload = formatted_payload(response.model_dump(), indent)
30
24
  click.echo(payload)
@@ -1,11 +1,10 @@
1
- import sys
2
-
3
1
  import click
4
- from click import ClickException
5
2
  from scim2_client import SCIMClientError
6
3
  from scim2_models import SearchRequest
7
4
  from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
8
5
 
6
+ from scim2_cli.utils import exception_to_click_error
7
+
9
8
  from .utils import DOC_URL
10
9
  from .utils import formatted_payload
11
10
 
@@ -93,13 +92,7 @@ def search_cli(
93
92
  )
94
93
 
95
94
  except SCIMClientError as scim_exc:
96
- message = str(scim_exc)
97
- if sys.version_info >= (3, 11) and hasattr(
98
- scim_exc, "__notes__"
99
- ): # pragma: no cover
100
- for note in scim_exc.__notes__:
101
- message = f"{message}\n{note}"
102
- raise ClickException(message) from scim_exc
95
+ raise exception_to_click_error(scim_exc) from scim_exc
103
96
 
104
97
  payload = formatted_payload(response.model_dump(), indent)
105
98
  click.echo(payload)
@@ -1,3 +1,5 @@
1
+ import sys
2
+
1
3
  import click
2
4
  from scim2_tester import Status
3
5
  from scim2_tester import check_server
@@ -10,7 +12,19 @@ from .utils import Color
10
12
  @click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="test")
11
13
  @click.pass_context
12
14
  @click.option("-v", "--verbose", is_flag=True, help="Enables verbose mode")
13
- def test_cli(ctx, verbose):
15
+ @click.option(
16
+ "--check-status-code/--dont-check-status-code",
17
+ is_flag=True,
18
+ default=True,
19
+ help="Fail on unexpected return codes.",
20
+ )
21
+ @click.option(
22
+ "--check-content-type/--dont-check-content-type",
23
+ is_flag=True,
24
+ default=True,
25
+ help="Fail on unexpected content types.",
26
+ )
27
+ def test_cli(ctx, verbose, check_status_code, check_content_type):
14
28
  """Perform a server SCIM compliance check using :doc:`scim2-tester <scim2_tester:index>`.
15
29
 
16
30
  .. code-block:: bash
@@ -18,16 +32,22 @@ def test_cli(ctx, verbose):
18
32
  test
19
33
  """
20
34
  client = ctx.obj["client"]
35
+ client.check_status_code = check_status_code
36
+ client.check_content_type = check_content_type
21
37
  results = check_server(client)
22
38
  click.echo(f"Performing a SCIM compliance check on {client.client.base_url} ...")
39
+ success = True
23
40
  for result in results:
24
41
  if result.status == Status.SUCCESS:
25
42
  status = click.style(result.status.name, fg=Color.green)
26
43
  else:
27
44
  status = click.style(result.status.name, fg=Color.red)
45
+ success = False
28
46
  click.echo(f"{status} {result.title}")
29
47
 
30
48
  if result.reason:
31
49
  click.echo(f" {result.reason}")
32
50
  if verbose and result.data:
33
51
  click.echo(f" {result.data}")
52
+
53
+ sys.exit(0 if success else 1)
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import sys
2
3
  from enum import Enum
3
4
 
4
5
  import click
@@ -104,3 +105,12 @@ def unacceptable_fields(context, model):
104
105
  for field_name in model.model_fields
105
106
  if not is_field_acceptable(context, model, field_name)
106
107
  ]
108
+
109
+
110
+ def exception_to_click_error(exception):
111
+ message = str(exception)
112
+ if sys.version_info >= (3, 11) and hasattr(
113
+ exception, "__notes__"
114
+ ): # pragma: no cover
115
+ message += "\n" + "\n".join(exception.__notes__)
116
+ return click.ClickException(message)
@@ -1,13 +1,16 @@
1
1
  import json
2
2
  import os
3
3
 
4
+ import pytest
4
5
  from scim2_models import AuthenticationScheme
5
6
  from scim2_models import Bulk
6
7
  from scim2_models import ChangePassword
7
8
  from scim2_models import ETag
8
9
  from scim2_models import Filter
10
+ from scim2_models import ListResponse
9
11
  from scim2_models import Patch
10
12
  from scim2_models import ResourceType
13
+ from scim2_models import Schema
11
14
  from scim2_models import ServiceProviderConfig
12
15
  from scim2_models import Sort
13
16
  from scim2_models import User
@@ -27,6 +30,13 @@ def test_help(runner):
27
30
  assert "SCIM application development CLI.\n" in result.output
28
31
 
29
32
 
33
+ def test_bad_server(runner):
34
+ """Test that the discovery step display a readable error when a bad server has been passed."""
35
+ result = runner.invoke(cli, ["--url", "http://scim.invalid", "query"])
36
+ assert result.exit_code == 1
37
+ assert "Error: Network error happened during request\n" in result.output
38
+
39
+
30
40
  def test_stdin_bad_json(runner, httpserver):
31
41
  """Test that invalid JSON stdin raise an error."""
32
42
  result = runner.invoke(
@@ -105,9 +115,9 @@ def test_env_vars(runner, httpserver, simple_user_payload):
105
115
  del os.environ["SCIM_CLI_HEADERS"]
106
116
 
107
117
 
108
- def test_custom_configuration(runner, httpserver, simple_user_payload, tmp_path):
109
- """Test passing custom .JSON configuration files to the command."""
110
- spc = ServiceProviderConfig(
118
+ @pytest.fixture
119
+ def service_provider_configuration():
120
+ return ServiceProviderConfig(
111
121
  documentation_uri="https://scim.test",
112
122
  patch=Patch(supported=False),
113
123
  bulk=Bulk(supported=False, max_operations=0, max_payload_size=0),
@@ -125,29 +135,48 @@ def test_custom_configuration(runner, httpserver, simple_user_payload, tmp_path)
125
135
  primary=True,
126
136
  ),
127
137
  ],
128
- ).model_dump()
138
+ )
129
139
 
130
- spc_path = tmp_path / "service_provider_configuration.json"
131
- with open(spc_path, "w") as fd:
132
- json.dump(spc, fd)
133
140
 
134
- schemas = [User.to_schema().model_dump()]
135
- schemas_path = tmp_path / "schemas.json"
136
- with open(schemas_path, "w") as fd:
137
- json.dump(schemas, fd)
141
+ @pytest.fixture
142
+ def schemas():
143
+ return [User.to_schema()]
138
144
 
139
- resource_types = [
145
+
146
+ @pytest.fixture
147
+ def resource_types():
148
+ return [
140
149
  ResourceType(
141
150
  id="User",
142
151
  name="User",
143
152
  endpoint="/somewhere-different",
144
153
  description="User accounts",
145
154
  schema_="urn:ietf:params:scim:schemas:core:2.0:User",
146
- ).model_dump()
155
+ )
147
156
  ]
157
+
158
+
159
+ def test_custom_configuration_by_parameter(
160
+ runner,
161
+ httpserver,
162
+ simple_user_payload,
163
+ tmp_path,
164
+ service_provider_configuration,
165
+ schemas,
166
+ resource_types,
167
+ ):
168
+ """Test passing custom .JSON configuration files to the command."""
169
+ spc_path = tmp_path / "service_provider_configuration.json"
170
+ with open(spc_path, "w") as fd:
171
+ json.dump(service_provider_configuration.model_dump(), fd)
172
+
173
+ schemas_path = tmp_path / "schemas.json"
174
+ with open(schemas_path, "w") as fd:
175
+ json.dump([schema.model_dump() for schema in schemas], fd)
176
+
148
177
  resource_types_path = tmp_path / "resource_types.json"
149
178
  with open(resource_types_path, "w") as fd:
150
- json.dump(resource_types, fd)
179
+ json.dump([resource_type.model_dump() for resource_type in resource_types], fd)
151
180
 
152
181
  httpserver.expect_request(
153
182
  "/somewhere-different/foobar",
@@ -176,3 +205,123 @@ def test_custom_configuration(runner, httpserver, simple_user_payload, tmp_path)
176
205
  catch_exceptions=False,
177
206
  )
178
207
  assert result.exit_code == 0
208
+
209
+
210
+ def test_custom_configuration_in_list_response(
211
+ runner,
212
+ httpserver,
213
+ simple_user_payload,
214
+ tmp_path,
215
+ service_provider_configuration,
216
+ schemas,
217
+ resource_types,
218
+ ):
219
+ """Test that ListResponse format is supported."""
220
+ spc_path = tmp_path / "service_provider_configuration.json"
221
+ with open(spc_path, "w") as fd:
222
+ json.dump(service_provider_configuration.model_dump(), fd)
223
+
224
+ schemas_path = tmp_path / "schemas.json"
225
+ with open(schemas_path, "w") as fd:
226
+ json.dump(
227
+ ListResponse[Schema](
228
+ total_results=len(schemas),
229
+ start_index=1,
230
+ items_per_page=len(schemas),
231
+ resources=schemas,
232
+ ).model_dump(),
233
+ fd,
234
+ )
235
+
236
+ resource_types_path = tmp_path / "resource_types.json"
237
+ with open(resource_types_path, "w") as fd:
238
+ json.dump(
239
+ ListResponse[ResourceType](
240
+ total_results=len(resource_types),
241
+ start_index=1,
242
+ items_per_page=len(resource_types),
243
+ resources=resource_types,
244
+ ).model_dump(),
245
+ fd,
246
+ )
247
+
248
+ httpserver.expect_request(
249
+ "/somewhere-different/foobar",
250
+ method="GET",
251
+ ).respond_with_json(
252
+ simple_user_payload("foobar"),
253
+ status=200,
254
+ content_type="application/scim+json",
255
+ )
256
+
257
+ result = runner.invoke(
258
+ cli,
259
+ [
260
+ "--url",
261
+ httpserver.url_for("/"),
262
+ "--service-provider-config",
263
+ spc_path,
264
+ "--schemas",
265
+ schemas_path,
266
+ "--resource-types",
267
+ resource_types_path,
268
+ "query",
269
+ "user",
270
+ "foobar",
271
+ ],
272
+ catch_exceptions=False,
273
+ )
274
+ assert result.exit_code == 0
275
+
276
+
277
+ def test_custom_configuration_by_env(
278
+ runner,
279
+ httpserver,
280
+ simple_user_payload,
281
+ tmp_path,
282
+ service_provider_configuration,
283
+ schemas,
284
+ resource_types,
285
+ ):
286
+ """Test passing custom .JSON configuration files to the command."""
287
+ spc_path = tmp_path / "service_provider_configuration.json"
288
+ with open(spc_path, "w") as fd:
289
+ json.dump(service_provider_configuration.model_dump(), fd)
290
+
291
+ schemas_path = tmp_path / "schemas.json"
292
+ with open(schemas_path, "w") as fd:
293
+ json.dump([schema.model_dump() for schema in schemas], fd)
294
+
295
+ resource_types_path = tmp_path / "resource_types.json"
296
+ with open(resource_types_path, "w") as fd:
297
+ json.dump([resource_type.model_dump() for resource_type in resource_types], fd)
298
+
299
+ httpserver.expect_request(
300
+ "/somewhere-different/foobar",
301
+ method="GET",
302
+ ).respond_with_json(
303
+ simple_user_payload("foobar"),
304
+ status=200,
305
+ content_type="application/scim+json",
306
+ )
307
+
308
+ os.environ["SCIM_CLI_SERVICE_PROVIDER_CONFIG"] = str(spc_path)
309
+ os.environ["SCIM_CLI_SCHEMAS"] = str(schemas_path)
310
+ os.environ["SCIM_CLI_RESOURCE_TYPES"] = str(resource_types_path)
311
+ try:
312
+ result = runner.invoke(
313
+ cli,
314
+ [
315
+ "--url",
316
+ httpserver.url_for("/"),
317
+ "query",
318
+ "user",
319
+ "foobar",
320
+ ],
321
+ catch_exceptions=False,
322
+ )
323
+ assert result.exit_code == 0
324
+ finally:
325
+ del os.environ["SCIM_CLI_SERVICE_PROVIDER_CONFIG"]
326
+ del os.environ["SCIM_CLI_SCHEMAS"]
327
+ del os.environ["SCIM_CLI_RESOURCE_TYPES"]
@@ -32,7 +32,6 @@ def test_nominal(runner, httpserver):
32
32
  ["--url", httpserver.url_for("/"), "test"],
33
33
  catch_exceptions=False,
34
34
  )
35
- assert result.exit_code == 0, result.stdout
36
35
 
37
36
  expected_output = f"""Performing a SCIM compliance check on http://localhost:{httpserver.port}/ ...
38
37
  SUCCESS test1
@@ -68,7 +67,6 @@ def test_verbose(runner, httpserver):
68
67
  ["--url", httpserver.url_for("/"), "test", "--verbose"],
69
68
  catch_exceptions=False,
70
69
  )
71
- assert result.exit_code == 0, result.stdout
72
70
 
73
71
  expected_output = f"""Performing a SCIM compliance check on http://localhost:{httpserver.port}/ ...
74
72
  SUCCESS test1
@@ -79,3 +77,12 @@ ERROR test2
79
77
  data2
80
78
  """
81
79
  assert result.output == expected_output
80
+
81
+
82
+ def test_failure(runner, httpserver):
83
+ """Test SCIM compliance test failure."""
84
+ result = runner.invoke(
85
+ cli,
86
+ ["--url", "http://scim.invalid", "test"],
87
+ )
88
+ assert result.exit_code == 1, result.stdout
@@ -30,17 +30,17 @@ wheels = [
30
30
 
31
31
  [[package]]
32
32
  name = "anyio"
33
- version = "4.6.2.post1"
33
+ version = "4.7.0"
34
34
  source = { registry = "https://pypi.org/simple" }
35
35
  dependencies = [
36
36
  { name = "exceptiongroup", marker = "python_full_version < '3.11'" },
37
37
  { name = "idna" },
38
38
  { name = "sniffio" },
39
- { name = "typing-extensions", marker = "python_full_version < '3.11'" },
39
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
40
40
  ]
41
- sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 }
41
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/40/318e58f669b1a9e00f5c4453910682e2d9dd594334539c7b7817dabb765f/anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", size = 177076 }
42
42
  wheels = [
43
- { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 },
43
+ { url = "https://files.pythonhosted.org/packages/a0/7a/4daaf3b6c08ad7ceffea4634ec206faeff697526421c20f07628c7372156/anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352", size = 93052 },
44
44
  ]
45
45
 
46
46
  [[package]]
@@ -931,7 +931,7 @@ wheels = [
931
931
 
932
932
  [[package]]
933
933
  name = "scim2-cli"
934
- version = "0.2.0"
934
+ version = "0.2.2"
935
935
  source = { editable = "." }
936
936
  dependencies = [
937
937
  { name = "click" },
@@ -967,7 +967,7 @@ requires-dist = [
967
967
  { name = "click", specifier = ">=8.1.7" },
968
968
  { name = "pydanclick", specifier = ">=0.3.0" },
969
969
  { name = "pygments", specifier = ">=2.18.0" },
970
- { name = "scim2-client", specifier = ">=0.4.2" },
970
+ { name = "scim2-client", specifier = ">=0.4.3" },
971
971
  { name = "scim2-tester", extras = ["httpx"], specifier = ">=0.1.10" },
972
972
  { name = "sphinx-click-rst-to-ansi-formatter", specifier = ">=0.1.0" },
973
973
  ]
@@ -992,14 +992,14 @@ doc = [
992
992
 
993
993
  [[package]]
994
994
  name = "scim2-client"
995
- version = "0.4.2"
995
+ version = "0.4.3"
996
996
  source = { registry = "https://pypi.org/simple" }
997
997
  dependencies = [
998
998
  { name = "scim2-models" },
999
999
  ]
1000
- sdist = { url = "https://files.pythonhosted.org/packages/29/da/73c47e87b06f96d717ed4d0c6d284f3028e65dbf9b4e9ba4fea0da1b312f/scim2_client-0.4.2.tar.gz", hash = "sha256:d765b3eb99d6f87fd8d8e4487b0c5683598099fc79514601f2a7328784b8c4d6", size = 85291 }
1000
+ sdist = { url = "https://files.pythonhosted.org/packages/c9/44/1b228a6a680ca96a1274f2ca1dd22aa3e61e656c5e829c348b27f793dc9d/scim2_client-0.4.3.tar.gz", hash = "sha256:69f55e1c296cb018cb4d71954485b6dab8153bb59935647b1e063a659c141ede", size = 85428 }
1001
1001
  wheels = [
1002
- { url = "https://files.pythonhosted.org/packages/00/3a/bbfd33b929e0292d85219b49b3a957c14d7679d3409235e309515b90785c/scim2_client-0.4.2-py3-none-any.whl", hash = "sha256:060c8724c03e3254d0f60191d78ffe39f5253a6c649fdfac1e605d117939ab6c", size = 22248 },
1002
+ { url = "https://files.pythonhosted.org/packages/fa/75/d56f022664d6db564b0e85265ab5e97b49133f796ff1d502bd652e7c075f/scim2_client-0.4.3-py3-none-any.whl", hash = "sha256:051578f4e56e57149b1b6ea06c30cd4d831b8f2278c301e92421b0ebf524b813", size = 22373 },
1003
1003
  ]
1004
1004
 
1005
1005
  [package.optional-dependencies]
@@ -1260,16 +1260,16 @@ wheels = [
1260
1260
 
1261
1261
  [[package]]
1262
1262
  name = "tox-uv"
1263
- version = "1.16.0"
1263
+ version = "1.16.1"
1264
1264
  source = { registry = "https://pypi.org/simple" }
1265
1265
  dependencies = [
1266
1266
  { name = "packaging" },
1267
1267
  { name = "tox" },
1268
1268
  { name = "uv" },
1269
1269
  ]
1270
- sdist = { url = "https://files.pythonhosted.org/packages/ed/5e/c3d2a45ab5465dddbbc267a589c9cfce23b91750d49af10738a08c98534e/tox_uv-1.16.0.tar.gz", hash = "sha256:71b2e2fa6c35c1360b91a302df1d65b3e5a1f656b321c5ebf7b84545804c9f01", size = 16337 }
1270
+ sdist = { url = "https://files.pythonhosted.org/packages/81/5a/5335fe0348d626a01e7a600e85cbea04e0f5f221e9c2f3a2b3664d774f69/tox_uv-1.16.1.tar.gz", hash = "sha256:63b0a872f1f97263f89fe41e7195b401a18a67a9a14e3803372091a364b43cd6", size = 16940 }
1271
1271
  wheels = [
1272
- { url = "https://files.pythonhosted.org/packages/ad/8d/1baa9f725ddd4824708759cf7b74bc43379f5f7feb079fde0629d7b32b3e/tox_uv-1.16.0-py3-none-any.whl", hash = "sha256:e6f0b525a687e745ab878d07cbf5c7e85d582028d4a7c8935f95e84350651432", size = 13661 },
1272
+ { url = "https://files.pythonhosted.org/packages/57/1d/c44b1e246f95fadf9fc83b34f70fb297466bc580b71d756e85097ef1dd6a/tox_uv-1.16.1-py3-none-any.whl", hash = "sha256:ab5dae5df02ca3b6c00f95c86810afe6c0e2b8ff8e6d76053ef677dcce38975b", size = 13681 },
1273
1273
  ]
1274
1274
 
1275
1275
  [[package]]
@@ -1292,27 +1292,27 @@ wheels = [
1292
1292
 
1293
1293
  [[package]]
1294
1294
  name = "uv"
1295
- version = "0.5.5"
1296
- source = { registry = "https://pypi.org/simple" }
1297
- sdist = { url = "https://files.pythonhosted.org/packages/46/95/ba000f161640015c9f2ecc2681f38eb161b8edac600a0451b36e4ad15aa4/uv-0.5.5.tar.gz", hash = "sha256:7f8db4bdf7eaef6be271457c4b2a167f41ad115434944a09f5034018a29b4093", size = 2324705 }
1298
- wheels = [
1299
- { url = "https://files.pythonhosted.org/packages/6d/91/ac61feea1b11e4dc342a4fd174f311b628a04ec3b4614a24676c4d214bd1/uv-0.5.5-py3-none-linux_armv6l.whl", hash = "sha256:d091e88a9c2c830169c3ccf95fd972759e0ab629dacc2d5eff525e5ba3583904", size = 13887911 },
1300
- { url = "https://files.pythonhosted.org/packages/84/18/461af22fd1f80f86548013639ab345810dbf35aa44dff1732c6faf311a48/uv-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f0bfc7ced2fe0c85b3070dfa219072a1406133e18aab2f2fe10b6455ede0f8b2", size = 13900075 },
1301
- { url = "https://files.pythonhosted.org/packages/68/5f/7a236ad48f81c580691f9e5f28dd47289a9819f18410f12ee3c621791efd/uv-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:553901e95cb5a4da1da19e288c29c5f886793f981750400e5cef48e3031b970b", size = 12869530 },
1302
- { url = "https://files.pythonhosted.org/packages/94/d6/d6f441fd041fb4883332b999481896e28502b51681bcb786a001fb1e4a50/uv-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a4f0c7647187044056dc6f6f5d31b01f445d8695eb7d2f442b29fd5c9216a56f", size = 13158009 },
1303
- { url = "https://files.pythonhosted.org/packages/9b/3d/cc1e44e14266bbdb71eb020d14454f4f24f72fcc2eb84a52bf809e030918/uv-0.5.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34e894c922ba29a59bbe812a458a7095a575f76b87dfc362e0c3f4f650d6f631", size = 13676515 },
1304
- { url = "https://files.pythonhosted.org/packages/10/5f/b81ed7ab715687a790b9b06a6f4e2781d7f7222840eead0392c3ef6f80ea/uv-0.5.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f5569798fc8eaad58fbb4fb70ced8f09ebe607fbbfb95fa42c559f57bbe0cabd", size = 14245499 },
1305
- { url = "https://files.pythonhosted.org/packages/f7/e1/ce5a88bc5ed61ee310a8e0acc3fa3032280bb2d49514fd48b801bb36f96f/uv-0.5.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:59d53cce11718ce5d5367afc8c93ebcfc5e1cddfa4a44aedbf08d08d9b738381", size = 14920230 },
1306
- { url = "https://files.pythonhosted.org/packages/03/84/6fa2deb5bed9dab815cb7f57e14885ecf8de22249b992f9c4ea5dd042c28/uv-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dee9517ebba13d07d8f139c439c5ff63e438d31ebda4d7eb0af8d0f0cc6a181", size = 14701845 },
1307
- { url = "https://files.pythonhosted.org/packages/59/b2/7078d52a73c1e13d984c22fae1888993e5cd3f40cea029ab2666d79d92a7/uv-0.5.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29286cd6b9f8e040d02894a67c6b6304811ea393ca9dfade109e93cf4b3b842c", size = 18963098 },
1308
- { url = "https://files.pythonhosted.org/packages/b9/89/a91d927574ce2103d22b7beab319468d915a1d04757cf82b6eaf79b56a10/uv-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f7f04ae5a5430873d8610d8ea0a5d35df92e60bf701f80b3cf24857e0ac5e72", size = 14442788 },
1309
- { url = "https://files.pythonhosted.org/packages/46/3d/2c5a9d362771aeef13c2ba1c9b1267b469e11331b874486f03f6c6fc5eb1/uv-0.5.5-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:5a47345ccafc0105b2f0cc22fcb0bb05be4d0e60df67f5beea28069b0bb372c8", size = 13394955 },
1310
- { url = "https://files.pythonhosted.org/packages/cf/92/8d646385401472e7358cdf0fc86015edc9bf9132c9b79d7e2a11c300dd84/uv-0.5.5-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:b55d507bfe2bd2330c41680e4b0077972381f40975a59b53007254196abc4477", size = 13639522 },
1311
- { url = "https://files.pythonhosted.org/packages/da/23/5e00b71f9c8b9c16f0947bf84b71265bafdf24947d2e271a657da00a0c6a/uv-0.5.5-py3-none-musllinux_1_1_i686.whl", hash = "sha256:365715e7247c2cd8ef661e8f96927b181248f689c07e48b076c9dbc78a4a0877", size = 13957451 },
1312
- { url = "https://files.pythonhosted.org/packages/02/38/0c624a8d89416fa6c849c6670066c74f420aa898eafdf7cefd3adf77d686/uv-0.5.5-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:0314a4b9a25bf00afe4e5472c338c8c6bd34688c23d63ce1ad35462cf087b492", size = 15840287 },
1313
- { url = "https://files.pythonhosted.org/packages/a8/bd/1a81700d2555be504f0b1ff82f86d666384805e354c8eec2fdf7558d0cf9/uv-0.5.5-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:f4e9ddcffc29f009f692cda699912b02f6a12089d741b71d2fcd0b181eb71c5d", size = 14560282 },
1314
- { url = "https://files.pythonhosted.org/packages/cd/71/c0b137c62a5a8ab5ecc0a8f505b77e5a05d5b94fd9da7560a247dac000f3/uv-0.5.5-py3-none-win32.whl", hash = "sha256:9af7018430da1f0960eee1592c820c343e2619f2d71f66c3be62da330826c537", size = 13810875 },
1315
- { url = "https://files.pythonhosted.org/packages/e3/d8/56dbbe07aba3cf561fff52b8ed75ce7b694a0710ab676fae4cd7fbf1bdae/uv-0.5.5-py3-none-win_amd64.whl", hash = "sha256:69e15f24493d86c3a2da3764891e35a033ceda09404c1f9b386671d509db95f3", size = 15604848 },
1295
+ version = "0.5.6"
1296
+ source = { registry = "https://pypi.org/simple" }
1297
+ sdist = { url = "https://files.pythonhosted.org/packages/d7/23/2c12f9941de6fd984259e778dc8660172fc6498a3c5a77bd1df81a6daf93/uv-0.5.6.tar.gz", hash = "sha256:9297f084ca2004044a9353525777c9cb1151a0d768de87cc3920574597674f88", size = 2343719 }
1298
+ wheels = [
1299
+ { url = "https://files.pythonhosted.org/packages/d9/e4/8a8e06c2e720ab2677d9457031e58c613005158bef61aa34d72c297c3476/uv-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:485decff72ba2ba6c8b82de0f8ecc877bd4080dc4e27604f3ebd9da98e00c53f", size = 14129515 },
1300
+ { url = "https://files.pythonhosted.org/packages/0f/fc/a6cd710c5c90eb03d7b9f3de1b88a8064685a266e65f3cb21653282e59b4/uv-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6e13c1bb1f00b596a50711afffc407570edf9825fea4cfbc1d80cfb59816e501", size = 14118111 },
1301
+ { url = "https://files.pythonhosted.org/packages/87/4f/5263768d16f2366e70b8b56c5066fde76f41ca66f7c088aaeb715b5b684d/uv-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4ca83b575d316030503d81d3cc85a53be45d8bab56892f845f7e545006e5df59", size = 13087256 },
1302
+ { url = "https://files.pythonhosted.org/packages/1e/b3/1fae9b18fbadf8ddf4817ccf10b8f5e69b9a3312f7609ebbc6aec9ca6998/uv-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a15ac68584b346e592ca4c5167ffd4c4ba9906706b423b696e7b37d03dea753b", size = 13351635 },
1303
+ { url = "https://files.pythonhosted.org/packages/3c/c5/f120169cab520992124718463bd24fbd4061380b909001fded2736376595/uv-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c7afc715f48a6ddc61257ed62c74595b5762eb67a6f23696fae6e609698a990", size = 13895585 },
1304
+ { url = "https://files.pythonhosted.org/packages/5c/e1/9142086ad917d20bd3cca4421ab68a5d894ea33199cb121f86a6aeac611f/uv-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b49db3c842dcdaccdaf8ea26770c2b18632528f68e7f3211415ae25796de71a", size = 14463583 },
1305
+ { url = "https://files.pythonhosted.org/packages/4f/88/4f7bb77c137f52af36ebf20d67ed629a23ab35d7f47c5162707cfa1c4cdf/uv-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9c1ebf58ef3906e05e0060cda4a801a45e93334169821a89effa6ba9607dfa07", size = 15162379 },
1306
+ { url = "https://files.pythonhosted.org/packages/f4/5d/e276d8bce24823f89db9faa03f325a0f67309e3ed4eac64e193a06f8cdf1/uv-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:848b12fe47ee8dd871ae5cfb1fefc2bac7cdb90bd34ff1f08bf78dd751f6950a", size = 14962665 },
1307
+ { url = "https://files.pythonhosted.org/packages/d3/48/b6cb8a56b1c0e49271d24dba7af3f6e9281617a44f7b2a900ca0f3a8bce0/uv-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e14da824a775abe4f28f03c4a672df99697fdd42c5e99df40d99a7d5c90e5c8", size = 19300290 },
1308
+ { url = "https://files.pythonhosted.org/packages/59/52/d7c8baab385fcfd8da93065e42993a4d0d57a97e600ab3c7b3de83f63e57/uv-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:920dd926d235f826454e7b68cb1890ded6e67510e6195c346eef41caabc9d9b7", size = 14623299 },
1309
+ { url = "https://files.pythonhosted.org/packages/f1/65/10ed431541d2bce7589715951371c2218deb3ad8fb8922d38a1283c9116b/uv-0.5.6-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:152466b8854bc30661620671cd1e5e854123e734fb4f380bc83eab5e3c4450a9", size = 13579816 },
1310
+ { url = "https://files.pythonhosted.org/packages/69/b3/9a5accd6da604564c9e448c515aca749a9e1f42a756d3b6c8091c7a8021f/uv-0.5.6-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:87b0b77ebf036363b8b0ed55c0f4a4a687b20500e3f9aa45574dfe05f9761057", size = 13875018 },
1311
+ { url = "https://files.pythonhosted.org/packages/9f/8d/81962b96c17c7c77bc54d5f74ecf55b4699da35988149b86dbf77f9b9795/uv-0.5.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:eae7301bd35891545a4e139b0f1bd5aa7981afba43654233297be463d6d6ba8b", size = 14197982 },
1312
+ { url = "https://files.pythonhosted.org/packages/f5/99/dc2113b4fd8232aacd75aa492f61f6a15fc55191967b31c46b3184aa6a99/uv-0.5.6-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:f2e6a13da16b8a3a43d50a1be5910c4e55b04889bcacfb8c9a22dcff817fa326", size = 16042590 },
1313
+ { url = "https://files.pythonhosted.org/packages/6a/08/477da5f4a6eac1da2a9a94a8a6e6e16c44d4a1d44c65cb0dfefa6165ccd4/uv-0.5.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2b1dc05b9b242989d4a6dc99916a0fd6dc98ea5146f757505da69ac20825067d", size = 14759319 },
1314
+ { url = "https://files.pythonhosted.org/packages/13/db/fdfa81cbeedea64cb7a376451a93d34c996d5656650d7a4578eff6464df6/uv-0.5.6-py3-none-win32.whl", hash = "sha256:8bc30ef6996c3352a45d75ebc9aeb2e7ae5ebb7b2118b6f036ddd02d4f689daa", size = 14054507 },
1315
+ { url = "https://files.pythonhosted.org/packages/83/1c/19f79f9df067694a20239c68f280efa4bfb25d4ab39442a96fae874ed839/uv-0.5.6-py3-none-win_amd64.whl", hash = "sha256:c1a3b180e90734812034cc1594256f519bff6c2715f3f1ceb9ac7bc578005f5d", size = 15915638 },
1316
1316
  ]
1317
1317
 
1318
1318
  [[package]]
@@ -1,47 +0,0 @@
1
- Tutorial
2
- ========
3
-
4
- The :ref:`cli` command allows you to interact with a SCIM server.
5
-
6
- Basic parameters
7
- ----------------
8
-
9
- In order to connect to a SCIM server you will need to pass the :option:`scim --url` parameter.
10
- You can also pass additional headers, such as authentication ones, with :option:`scim --url`.
11
-
12
- .. code-block:: shell
13
-
14
- $ scim2 --url https://auth.example --header "Authorization: Bearer 12345" create user --user-name "bjensen@example.com"
15
- {
16
- "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
17
- "id": "2819c223-7f76-453a-919d-413861904646",
18
- "userName": "bjensen@example.com",
19
- "meta": {
20
- "resourceType": "User",
21
- "created": "2010-01-23T04:56:22Z",
22
- "lastModified": "2011-05-13T04:42:34Z",
23
- "version": 'W\\/"3694e05e9dff590"',
24
- "location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
25
- },
26
- }
27
-
28
- However passing those parameters each time you use the command can be annoying.
29
- To make commands shorter, you can set those parameters once for all by using the :ref:`SCIM_CLI_URL <scim-url-scim_cli_url>` and :ref:`SCIM_CLI_HEADERS <scim-header-scim_cli_headers>` environment vars.
30
-
31
- .. code-block:: shell
32
-
33
- $ export SCIM_CLI_URL="https://auth.example"
34
- $ export SCIM_CLI_HEADERS="Authorization: Bearer 12345"
35
- $ scim2 create user --user-name "bjensen@example.com"
36
- {
37
- "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
38
- "id": "2819c223-7f76-453a-919d-413861904646",
39
- "userName": "bjensen@example.com",
40
- "meta": {
41
- "resourceType": "User",
42
- "created": "2010-01-23T04:56:22Z",
43
- "lastModified": "2011-05-13T04:42:34Z",
44
- "version": 'W\\/"3694e05e9dff590"',
45
- "location": "https://example.com/v2/Users/2819c223-7f76-453a-919d-413861904646",
46
- },
47
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes