scim2-cli 0.2.4__tar.gz → 0.4.0__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.
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/PKG-INFO +22 -43
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/README.md +15 -35
- scim2_cli-0.4.0/pyproject.toml +239 -0
- scim2_cli-0.2.4/pyproject.toml → scim2_cli-0.4.0/pyproject.toml.orig +33 -13
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/scim2_cli/__init__.py +44 -62
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/scim2_cli/create.py +4 -3
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/scim2_cli/delete.py +2 -2
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/scim2_cli/query.py +31 -3
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/scim2_cli/replace.py +4 -3
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/scim2_cli/search.py +2 -2
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/scim2_cli/test.py +6 -5
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/scim2_cli/utils.py +32 -7
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/LICENSE.md +0 -0
- {scim2_cli-0.2.4 → scim2_cli-0.4.0}/scim2_cli/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: scim2-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: SCIM application development CLI
|
|
5
5
|
Keywords: scim,scim2,provisioning,rfc7643,rfc7644
|
|
6
6
|
Author: Yaal Coop
|
|
@@ -208,7 +208,6 @@ License: Apache License
|
|
|
208
208
|
limitations under the License.
|
|
209
209
|
Classifier: Intended Audience :: Developers
|
|
210
210
|
Classifier: Development Status :: 3 - Alpha
|
|
211
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
212
211
|
Classifier: Programming Language :: Python :: 3.11
|
|
213
212
|
Classifier: Programming Language :: Python :: 3.12
|
|
214
213
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -219,16 +218,16 @@ Classifier: Environment :: Web Environment
|
|
|
219
218
|
Classifier: Programming Language :: Python
|
|
220
219
|
Classifier: Operating System :: OS Independent
|
|
221
220
|
Requires-Dist: click>=8.1.7
|
|
222
|
-
Requires-Dist: scim2-client>=0.
|
|
223
|
-
Requires-Dist: scim2-tester
|
|
221
|
+
Requires-Dist: scim2-client[httpx2]>=0.9.0
|
|
222
|
+
Requires-Dist: scim2-tester>=0.4.0
|
|
224
223
|
Requires-Dist: sphinx-click-rst-to-ansi-formatter>=0.1.0
|
|
225
|
-
Requires-Dist: pydanclick>=0.
|
|
224
|
+
Requires-Dist: pydanclick>=0.5.0
|
|
226
225
|
Requires-Dist: pygments>=2.18.0
|
|
227
|
-
Requires-Python: >=3.
|
|
228
|
-
Project-URL: changelog, https://scim2-cli.readthedocs.io/en/latest/changelog.html
|
|
226
|
+
Requires-Python: >=3.11
|
|
229
227
|
Project-URL: documentation, https://scim2-cli.readthedocs.io
|
|
230
|
-
Project-URL: funding, https://github.com/sponsors/python-scim
|
|
231
228
|
Project-URL: repository, https://github.com/python-scim/scim2-cli
|
|
229
|
+
Project-URL: changelog, https://scim2-cli.readthedocs.io/en/latest/changelog.html
|
|
230
|
+
Project-URL: funding, https://github.com/sponsors/python-scim
|
|
232
231
|
Description-Content-Type: text/markdown
|
|
233
232
|
|
|
234
233
|
# scim2-cli
|
|
@@ -242,44 +241,24 @@ Provisioning is the action of managing a set of resources across different servi
|
|
|
242
241
|
SCIM is often used between Identity Providers and applications in completion of standards like OAuth2 and OpenID Connect.
|
|
243
242
|
It allows users and groups creations, modifications and deletions to be synchronized between applications.
|
|
244
243
|
|
|
245
|
-
##
|
|
244
|
+
## Features
|
|
246
245
|
|
|
247
|
-
|
|
246
|
+
- **CRUD Commands**: `create`, `query`, `replace` and `delete` resources from the command line
|
|
247
|
+
- **Search Command**: Query resources using SCIM filters, sorting and pagination
|
|
248
|
+
- **Compliance Testing**: Built-in `test` command using [scim2-tester](https://scim2-tester.readthedocs.io) to validate server RFC compliance
|
|
249
|
+
- **Server Discovery**: Automatic retrieval of server schemas and resource types
|
|
250
|
+
- **Dynamic CLI Options**: Command options generated from server schemas
|
|
251
|
+
- **Flexible Input**: Pass attributes as CLI options or pipe JSON payloads via stdin
|
|
252
|
+
- **JSON Output**: Formatted JSON responses with optional indentation control
|
|
253
|
+
- **Authentication**: Custom HTTP headers support for Bearer tokens and other auth methods
|
|
248
254
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
### Python package
|
|
252
|
-
|
|
253
|
-
scim2-cli is published on the [PyPI repository](https://pypi.org/project/scim2-cli).
|
|
254
|
-
You can install it with your favourite python package manager.
|
|
255
|
-
|
|
256
|
-
```shell
|
|
257
|
-
pip install scim2-cli
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### From sources
|
|
261
|
-
|
|
262
|
-
To run scim2-cli from the sources, [uv is needed](https://docs.astral.sh/uv/getting-started/installation/) for development:
|
|
263
|
-
|
|
264
|
-
```shell
|
|
265
|
-
git clone https://github.com/python-scim/scim2-cli.git
|
|
266
|
-
cd scim2-cli
|
|
267
|
-
uv sync
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
Then, it can be launched directly as a Python script:
|
|
255
|
+
## Installation
|
|
271
256
|
|
|
272
|
-
```
|
|
273
|
-
|
|
257
|
+
```console
|
|
258
|
+
$ curl -LsSf uvx.sh/scim2-cli/install.sh | sh
|
|
274
259
|
```
|
|
275
260
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
```shell
|
|
279
|
-
uv sync --group bundle
|
|
280
|
-
uv run pyinstaller --name scim2 --onefile scim2_cli/__init__.py
|
|
281
|
-
./dist/scim2 --help
|
|
282
|
-
```
|
|
261
|
+
Check the [installation documentation](https://scim2-cli.readthedocs.io/en/latest/installation.html) for other methods.
|
|
283
262
|
|
|
284
263
|
## Usage
|
|
285
264
|
|
|
@@ -287,7 +266,7 @@ Check the [reference](https://scim2-cli.readthedocs.io/en/latest/reference.html)
|
|
|
287
266
|
|
|
288
267
|
Here is an example of resource creation:
|
|
289
268
|
|
|
290
|
-
```
|
|
269
|
+
```console
|
|
291
270
|
$ scim2 --url https://auth.example --header "Authorization: Bearer 12345" create user --user-name "bjensen@example.com"
|
|
292
271
|
{
|
|
293
272
|
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
@@ -305,7 +284,7 @@ $ scim2 --url https://auth.example --header "Authorization: Bearer 12345" create
|
|
|
305
284
|
|
|
306
285
|
Here is an example of resource query:
|
|
307
286
|
|
|
308
|
-
```
|
|
287
|
+
```console
|
|
309
288
|
$ scim2 --url https://auth.example --header "Authorization: Bearer 12345" query user 2819c223-7f76-453a-919d-413861904646
|
|
310
289
|
{
|
|
311
290
|
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
@@ -9,44 +9,24 @@ Provisioning is the action of managing a set of resources across different servi
|
|
|
9
9
|
SCIM is often used between Identity Providers and applications in completion of standards like OAuth2 and OpenID Connect.
|
|
10
10
|
It allows users and groups creations, modifications and deletions to be synchronized between applications.
|
|
11
11
|
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
### Binary files
|
|
15
|
-
|
|
16
|
-
Binary files are available on the [releases page](https://github.com/python-scim/scim2-cli/releases).
|
|
17
|
-
|
|
18
|
-
### Python package
|
|
19
|
-
|
|
20
|
-
scim2-cli is published on the [PyPI repository](https://pypi.org/project/scim2-cli).
|
|
21
|
-
You can install it with your favourite python package manager.
|
|
22
|
-
|
|
23
|
-
```shell
|
|
24
|
-
pip install scim2-cli
|
|
25
|
-
```
|
|
12
|
+
## Features
|
|
26
13
|
|
|
27
|
-
|
|
14
|
+
- **CRUD Commands**: `create`, `query`, `replace` and `delete` resources from the command line
|
|
15
|
+
- **Search Command**: Query resources using SCIM filters, sorting and pagination
|
|
16
|
+
- **Compliance Testing**: Built-in `test` command using [scim2-tester](https://scim2-tester.readthedocs.io) to validate server RFC compliance
|
|
17
|
+
- **Server Discovery**: Automatic retrieval of server schemas and resource types
|
|
18
|
+
- **Dynamic CLI Options**: Command options generated from server schemas
|
|
19
|
+
- **Flexible Input**: Pass attributes as CLI options or pipe JSON payloads via stdin
|
|
20
|
+
- **JSON Output**: Formatted JSON responses with optional indentation control
|
|
21
|
+
- **Authentication**: Custom HTTP headers support for Bearer tokens and other auth methods
|
|
28
22
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```shell
|
|
32
|
-
git clone https://github.com/python-scim/scim2-cli.git
|
|
33
|
-
cd scim2-cli
|
|
34
|
-
uv sync
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Then, it can be launched directly as a Python script:
|
|
23
|
+
## Installation
|
|
38
24
|
|
|
39
|
-
```
|
|
40
|
-
|
|
25
|
+
```console
|
|
26
|
+
$ curl -LsSf uvx.sh/scim2-cli/install.sh | sh
|
|
41
27
|
```
|
|
42
28
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```shell
|
|
46
|
-
uv sync --group bundle
|
|
47
|
-
uv run pyinstaller --name scim2 --onefile scim2_cli/__init__.py
|
|
48
|
-
./dist/scim2 --help
|
|
49
|
-
```
|
|
29
|
+
Check the [installation documentation](https://scim2-cli.readthedocs.io/en/latest/installation.html) for other methods.
|
|
50
30
|
|
|
51
31
|
## Usage
|
|
52
32
|
|
|
@@ -54,7 +34,7 @@ Check the [reference](https://scim2-cli.readthedocs.io/en/latest/reference.html)
|
|
|
54
34
|
|
|
55
35
|
Here is an example of resource creation:
|
|
56
36
|
|
|
57
|
-
```
|
|
37
|
+
```console
|
|
58
38
|
$ scim2 --url https://auth.example --header "Authorization: Bearer 12345" create user --user-name "bjensen@example.com"
|
|
59
39
|
{
|
|
60
40
|
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
@@ -72,7 +52,7 @@ $ scim2 --url https://auth.example --header "Authorization: Bearer 12345" create
|
|
|
72
52
|
|
|
73
53
|
Here is an example of resource query:
|
|
74
54
|
|
|
75
|
-
```
|
|
55
|
+
```console
|
|
76
56
|
$ scim2 --url https://auth.example --header "Authorization: Bearer 12345" query user 2819c223-7f76-453a-919d-413861904646
|
|
77
57
|
{
|
|
78
58
|
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.12.13,<0.13.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "scim2-cli"
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
description = "SCIM application development CLI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
keywords = [
|
|
11
|
+
"scim",
|
|
12
|
+
"scim2",
|
|
13
|
+
"provisioning",
|
|
14
|
+
"rfc7643",
|
|
15
|
+
"rfc7644",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
25
|
+
"License :: OSI Approved :: Apache Software License",
|
|
26
|
+
"Environment :: Web Environment",
|
|
27
|
+
"Programming Language :: Python",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
]
|
|
30
|
+
requires-python = ">= 3.11"
|
|
31
|
+
dependencies = [
|
|
32
|
+
"click>=8.1.7",
|
|
33
|
+
"scim2-client[httpx2]>=0.9.0",
|
|
34
|
+
"scim2-tester>=0.4.0",
|
|
35
|
+
"sphinx-click-rst-to-ansi-formatter>=0.1.0",
|
|
36
|
+
"pydanclick>=0.5.0",
|
|
37
|
+
"pygments>=2.18.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[[project.authors]]
|
|
41
|
+
name = "Yaal Coop"
|
|
42
|
+
email = "contact@yaal.coop"
|
|
43
|
+
|
|
44
|
+
[project.license]
|
|
45
|
+
file = "LICENSE.md"
|
|
46
|
+
|
|
47
|
+
[project.urls]
|
|
48
|
+
documentation = "https://scim2-cli.readthedocs.io"
|
|
49
|
+
repository = "https://github.com/python-scim/scim2-cli"
|
|
50
|
+
changelog = "https://scim2-cli.readthedocs.io/en/latest/changelog.html"
|
|
51
|
+
funding = "https://github.com/sponsors/python-scim"
|
|
52
|
+
|
|
53
|
+
[project.scripts]
|
|
54
|
+
scim = "scim2_cli:cli"
|
|
55
|
+
scim2 = "scim2_cli:cli"
|
|
56
|
+
|
|
57
|
+
[dependency-groups]
|
|
58
|
+
dev = [
|
|
59
|
+
"mypy>=1.13.0",
|
|
60
|
+
"prek>=0.1.0",
|
|
61
|
+
"pytest>=8.2.1",
|
|
62
|
+
"pytest-cov>=6.0.0",
|
|
63
|
+
"pytest-httpserver>=1.0.10",
|
|
64
|
+
"tox-uv>=1.16.0",
|
|
65
|
+
]
|
|
66
|
+
doc = [
|
|
67
|
+
"myst-parser>=3.0.1",
|
|
68
|
+
"shibuya>=2024.5.15",
|
|
69
|
+
"sphinx>=7.3.7",
|
|
70
|
+
"sphinx-click>=6.0.0",
|
|
71
|
+
"sphinx-design>=0.6.0",
|
|
72
|
+
"sphinx-iconify>=0.0.6",
|
|
73
|
+
"sphinx-issues>=5.0.0",
|
|
74
|
+
"sphinx-substitution-extensions>=2024.8.6",
|
|
75
|
+
]
|
|
76
|
+
bundle = ["pyinstaller>=6.10.0"]
|
|
77
|
+
|
|
78
|
+
[tool.uv]
|
|
79
|
+
exclude-newer = "14 days"
|
|
80
|
+
|
|
81
|
+
[tool.uv.exclude-newer-package]
|
|
82
|
+
scim2-models = false
|
|
83
|
+
scim2-client = false
|
|
84
|
+
scim2-server = false
|
|
85
|
+
scim2-cli = false
|
|
86
|
+
scim2-tester = false
|
|
87
|
+
pytest-scim2-server = false
|
|
88
|
+
|
|
89
|
+
[tool.uv.build-backend]
|
|
90
|
+
module-root = ""
|
|
91
|
+
|
|
92
|
+
[tool.coverage.run]
|
|
93
|
+
source = [
|
|
94
|
+
"scim2_cli",
|
|
95
|
+
"tests",
|
|
96
|
+
]
|
|
97
|
+
omit = [".tox/*"]
|
|
98
|
+
branch = true
|
|
99
|
+
|
|
100
|
+
[tool.coverage.report]
|
|
101
|
+
exclude_lines = [
|
|
102
|
+
"@pytest.mark.skip",
|
|
103
|
+
"pragma: no cover",
|
|
104
|
+
"raise NotImplementedError",
|
|
105
|
+
"except ImportError",
|
|
106
|
+
"if app.debug",
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
[tool.ruff.lint]
|
|
110
|
+
select = [
|
|
111
|
+
"B",
|
|
112
|
+
"D",
|
|
113
|
+
"E",
|
|
114
|
+
"F",
|
|
115
|
+
"I",
|
|
116
|
+
"UP",
|
|
117
|
+
]
|
|
118
|
+
ignore = [
|
|
119
|
+
"E501",
|
|
120
|
+
"E722",
|
|
121
|
+
"D100",
|
|
122
|
+
"D101",
|
|
123
|
+
"D102",
|
|
124
|
+
"D103",
|
|
125
|
+
"D104",
|
|
126
|
+
"D105",
|
|
127
|
+
"D106",
|
|
128
|
+
"D107",
|
|
129
|
+
"D203",
|
|
130
|
+
"D213",
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
[tool.ruff.lint.isort]
|
|
134
|
+
force-single-line = true
|
|
135
|
+
|
|
136
|
+
[tool.ruff.format]
|
|
137
|
+
docstring-code-format = true
|
|
138
|
+
|
|
139
|
+
[tool.codespell]
|
|
140
|
+
ignore-words-list = "datas"
|
|
141
|
+
|
|
142
|
+
[tool.mypy]
|
|
143
|
+
plugins = ["pydantic.mypy"]
|
|
144
|
+
|
|
145
|
+
[tool.pytest.ini_options]
|
|
146
|
+
testpaths = ["tests"]
|
|
147
|
+
|
|
148
|
+
[tool.tox]
|
|
149
|
+
requires = ["tox>=4.19"]
|
|
150
|
+
env_list = [
|
|
151
|
+
"style",
|
|
152
|
+
"py311",
|
|
153
|
+
"py312",
|
|
154
|
+
"py313",
|
|
155
|
+
"py314",
|
|
156
|
+
"minversions",
|
|
157
|
+
"doc",
|
|
158
|
+
"bundle",
|
|
159
|
+
"coverage",
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
[tool.tox.env_run_base]
|
|
163
|
+
runner = "uv-venv-lock-runner"
|
|
164
|
+
dependency_groups = ["dev"]
|
|
165
|
+
commands = [[
|
|
166
|
+
"pytest",
|
|
167
|
+
"--showlocals",
|
|
168
|
+
"--full-trace",
|
|
169
|
+
"{posargs}",
|
|
170
|
+
]]
|
|
171
|
+
|
|
172
|
+
[tool.tox.env.style]
|
|
173
|
+
runner = "uv-venv-runner"
|
|
174
|
+
commands = [[
|
|
175
|
+
"prek",
|
|
176
|
+
"run",
|
|
177
|
+
"--all-files",
|
|
178
|
+
"--show-diff-on-failure",
|
|
179
|
+
]]
|
|
180
|
+
|
|
181
|
+
[tool.tox.env.minversions]
|
|
182
|
+
runner = "uv-venv-runner"
|
|
183
|
+
uv_resolution = "lowest-direct"
|
|
184
|
+
|
|
185
|
+
[tool.tox.env.doc]
|
|
186
|
+
dependency_groups = ["doc"]
|
|
187
|
+
commands = [
|
|
188
|
+
[
|
|
189
|
+
"sphinx-build",
|
|
190
|
+
"-W",
|
|
191
|
+
"--keep-going",
|
|
192
|
+
"--builder",
|
|
193
|
+
"html",
|
|
194
|
+
"doc",
|
|
195
|
+
"build/sphinx/html",
|
|
196
|
+
],
|
|
197
|
+
[
|
|
198
|
+
"sphinx-build",
|
|
199
|
+
"-W",
|
|
200
|
+
"--keep-going",
|
|
201
|
+
"--builder",
|
|
202
|
+
"man",
|
|
203
|
+
"doc",
|
|
204
|
+
"build/sphinx/html",
|
|
205
|
+
],
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
[tool.tox.env.coverage]
|
|
209
|
+
commands = [
|
|
210
|
+
[
|
|
211
|
+
"pytest",
|
|
212
|
+
"--cov",
|
|
213
|
+
"--cov-fail-under=100",
|
|
214
|
+
"--cov-report",
|
|
215
|
+
"term:skip-covered",
|
|
216
|
+
"{posargs}",
|
|
217
|
+
],
|
|
218
|
+
[
|
|
219
|
+
"coverage",
|
|
220
|
+
"html",
|
|
221
|
+
],
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
[tool.tox.env.bundle]
|
|
225
|
+
dependency_groups = ["bundle"]
|
|
226
|
+
allowlist_externals = ["./dist/scim2"]
|
|
227
|
+
commands = [
|
|
228
|
+
[
|
|
229
|
+
"pyinstaller",
|
|
230
|
+
"--clean",
|
|
231
|
+
"--log-level",
|
|
232
|
+
"WARN",
|
|
233
|
+
"scim2.spec",
|
|
234
|
+
],
|
|
235
|
+
[
|
|
236
|
+
"./dist/scim2",
|
|
237
|
+
"--help",
|
|
238
|
+
],
|
|
239
|
+
]
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["uv_build>=0.
|
|
2
|
+
requires = ["uv_build>=0.12.13,<0.13.0"]
|
|
3
3
|
build-backend = "uv_build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scim2-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "SCIM application development CLI"
|
|
9
9
|
authors = [{name="Yaal Coop", email="contact@yaal.coop"}]
|
|
10
10
|
license = {file = "LICENSE.md"}
|
|
@@ -13,7 +13,6 @@ keywords = ["scim", "scim2", "provisioning", "rfc7643", "rfc7644"]
|
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Intended Audience :: Developers",
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
16
|
-
"Programming Language :: Python :: 3.10",
|
|
17
16
|
"Programming Language :: Python :: 3.11",
|
|
18
17
|
"Programming Language :: Python :: 3.12",
|
|
19
18
|
"Programming Language :: Python :: 3.13",
|
|
@@ -25,13 +24,13 @@ classifiers = [
|
|
|
25
24
|
"Operating System :: OS Independent",
|
|
26
25
|
]
|
|
27
26
|
|
|
28
|
-
requires-python = ">= 3.
|
|
27
|
+
requires-python = ">= 3.11"
|
|
29
28
|
dependencies = [
|
|
30
29
|
"click>=8.1.7",
|
|
31
|
-
"scim2-client>=0.
|
|
32
|
-
"scim2-tester
|
|
30
|
+
"scim2-client[httpx2]>=0.9.0",
|
|
31
|
+
"scim2-tester>=0.4.0",
|
|
33
32
|
"sphinx-click-rst-to-ansi-formatter>=0.1.0",
|
|
34
|
-
"pydanclick>=0.
|
|
33
|
+
"pydanclick>=0.5.0",
|
|
35
34
|
"pygments>=2.18.0",
|
|
36
35
|
]
|
|
37
36
|
|
|
@@ -46,16 +45,19 @@ dev = [
|
|
|
46
45
|
"mypy>=1.13.0",
|
|
47
46
|
"prek>=0.1.0",
|
|
48
47
|
"pytest>=8.2.1",
|
|
49
|
-
"pytest-
|
|
48
|
+
"pytest-cov>=6.0.0",
|
|
50
49
|
"pytest-httpserver>=1.0.10",
|
|
51
50
|
"tox-uv>=1.16.0",
|
|
52
51
|
]
|
|
53
52
|
doc = [
|
|
53
|
+
"myst-parser>=3.0.1",
|
|
54
54
|
"shibuya>=2024.5.15",
|
|
55
55
|
"sphinx>=7.3.7",
|
|
56
56
|
"sphinx-click>=6.0.0",
|
|
57
|
-
"sphinx-
|
|
58
|
-
"
|
|
57
|
+
"sphinx-design>=0.6.0",
|
|
58
|
+
"sphinx-iconify>=0.0.6",
|
|
59
|
+
"sphinx-issues>=5.0.0",
|
|
60
|
+
"sphinx-substitution-extensions>=2024.8.6",
|
|
59
61
|
]
|
|
60
62
|
bundle = [
|
|
61
63
|
"pyinstaller>=6.10.0",
|
|
@@ -65,6 +67,10 @@ bundle = [
|
|
|
65
67
|
scim = "scim2_cli:cli"
|
|
66
68
|
scim2 = "scim2_cli:cli"
|
|
67
69
|
|
|
70
|
+
[tool.uv]
|
|
71
|
+
exclude-newer = "14 days"
|
|
72
|
+
exclude-newer-package = { scim2-models = false, scim2-client = false, scim2-server = false, scim2-cli = false, scim2-tester = false, pytest-scim2-server = false }
|
|
73
|
+
|
|
68
74
|
[tool.uv.build-backend]
|
|
69
75
|
module-root = ""
|
|
70
76
|
|
|
@@ -115,22 +121,28 @@ force-single-line = true
|
|
|
115
121
|
[tool.ruff.format]
|
|
116
122
|
docstring-code-format = true
|
|
117
123
|
|
|
124
|
+
[tool.codespell]
|
|
125
|
+
ignore-words-list = "datas"
|
|
126
|
+
|
|
118
127
|
[tool.mypy]
|
|
119
128
|
plugins = [
|
|
120
129
|
"pydantic.mypy"
|
|
121
130
|
]
|
|
122
131
|
|
|
132
|
+
[tool.pytest.ini_options]
|
|
133
|
+
testpaths = ["tests"]
|
|
134
|
+
|
|
123
135
|
[tool.tox]
|
|
124
136
|
requires = ["tox>=4.19"]
|
|
125
137
|
env_list = [
|
|
126
138
|
"style",
|
|
127
|
-
"py310",
|
|
128
139
|
"py311",
|
|
129
140
|
"py312",
|
|
130
141
|
"py313",
|
|
131
142
|
"py314",
|
|
132
143
|
"minversions",
|
|
133
144
|
"doc",
|
|
145
|
+
"bundle",
|
|
134
146
|
"coverage",
|
|
135
147
|
]
|
|
136
148
|
|
|
@@ -154,8 +166,8 @@ uv_resolution = "lowest-direct"
|
|
|
154
166
|
[tool.tox.env.doc]
|
|
155
167
|
dependency_groups = ["doc"]
|
|
156
168
|
commands = [
|
|
157
|
-
["sphinx-build", "--builder", "html", "doc", "build/sphinx/html"],
|
|
158
|
-
["sphinx-build", "--builder", "man", "doc", "build/sphinx/html"],
|
|
169
|
+
["sphinx-build", "-W", "--keep-going", "--builder", "html", "doc", "build/sphinx/html"],
|
|
170
|
+
["sphinx-build", "-W", "--keep-going", "--builder", "man", "doc", "build/sphinx/html"],
|
|
159
171
|
]
|
|
160
172
|
|
|
161
173
|
[tool.tox.env.coverage]
|
|
@@ -163,3 +175,11 @@ commands = [
|
|
|
163
175
|
["pytest", "--cov", "--cov-fail-under=100", "--cov-report", "term:skip-covered", "{posargs}"],
|
|
164
176
|
["coverage", "html"],
|
|
165
177
|
]
|
|
178
|
+
|
|
179
|
+
[tool.tox.env.bundle]
|
|
180
|
+
dependency_groups = ["bundle"]
|
|
181
|
+
allowlist_externals = ["./dist/scim2"]
|
|
182
|
+
commands = [
|
|
183
|
+
["pyinstaller", "--clean", "--log-level", "WARN", "scim2.spec"],
|
|
184
|
+
["./dist/scim2", "--help"],
|
|
185
|
+
]
|
|
@@ -2,16 +2,14 @@ import json
|
|
|
2
2
|
import re
|
|
3
3
|
|
|
4
4
|
import click
|
|
5
|
-
from
|
|
6
|
-
from scim2_client import
|
|
7
|
-
from scim2_client.engines.httpx import SyncSCIMClient
|
|
8
|
-
from scim2_models import Group
|
|
5
|
+
from httpx2 import Client
|
|
6
|
+
from scim2_client.engines.httpx2 import SyncSCIMClient
|
|
9
7
|
from scim2_models import ListResponse
|
|
10
|
-
from scim2_models import Resource
|
|
11
8
|
from scim2_models import ResourceType
|
|
12
9
|
from scim2_models import Schema
|
|
10
|
+
from scim2_models import ScimProvider
|
|
11
|
+
from scim2_models import ScimProviderError
|
|
13
12
|
from scim2_models import ServiceProviderConfig
|
|
14
|
-
from scim2_models import User
|
|
15
13
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
16
14
|
|
|
17
15
|
from scim2_cli.create import create_cli
|
|
@@ -21,49 +19,41 @@ from scim2_cli.replace import replace_cli
|
|
|
21
19
|
from scim2_cli.search import search_cli
|
|
22
20
|
from scim2_cli.test import test_cli
|
|
23
21
|
from scim2_cli.utils import DOC_URL
|
|
22
|
+
from scim2_cli.utils import SCIM_EXCEPTIONS
|
|
24
23
|
from scim2_cli.utils import HeaderType
|
|
24
|
+
from scim2_cli.utils import escape_control_characters
|
|
25
25
|
from scim2_cli.utils import exception_to_click_error
|
|
26
26
|
from scim2_cli.utils import split_headers
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
else:
|
|
58
|
-
resource_types = None
|
|
59
|
-
|
|
60
|
-
if service_provider_config_fd:
|
|
61
|
-
spc_payload = json.load(service_provider_config_fd)
|
|
62
|
-
service_provider_config = ServiceProviderConfig.model_validate(spc_payload)
|
|
63
|
-
else:
|
|
64
|
-
service_provider_config = None
|
|
65
|
-
|
|
66
|
-
return resource_models, resource_types, service_provider_config
|
|
29
|
+
def load_objects(fd, model):
|
|
30
|
+
"""Read a list of objects, bare or wrapped in a ListResponse, from a JSON file."""
|
|
31
|
+
payload = json.load(fd)
|
|
32
|
+
if isinstance(payload, dict):
|
|
33
|
+
return ListResponse[model].model_validate(payload).resources or []
|
|
34
|
+
return [model.model_validate(item) for item in payload]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def describe_server(
|
|
38
|
+
scim_client, schemas_fd, resource_types_fd, service_provider_config_fd
|
|
39
|
+
) -> ScimProvider:
|
|
40
|
+
"""Describe the server with the configuration files, and query it for the others."""
|
|
41
|
+
resource_types = (
|
|
42
|
+
load_objects(resource_types_fd, ResourceType)
|
|
43
|
+
if resource_types_fd
|
|
44
|
+
else scim_client.query(ResourceType).resources or []
|
|
45
|
+
)
|
|
46
|
+
schemas = (
|
|
47
|
+
load_objects(schemas_fd, Schema)
|
|
48
|
+
if schemas_fd
|
|
49
|
+
else scim_client.query(Schema).resources or []
|
|
50
|
+
)
|
|
51
|
+
config = (
|
|
52
|
+
ServiceProviderConfig.model_validate(json.load(service_provider_config_fd))
|
|
53
|
+
if service_provider_config_fd
|
|
54
|
+
else scim_client.query(ServiceProviderConfig)
|
|
55
|
+
)
|
|
56
|
+
return ScimProvider.from_discovery(schemas, resource_types, config)
|
|
67
57
|
|
|
68
58
|
|
|
69
59
|
@click.group(cls=make_rst_to_ansi_formatter(DOC_URL, group=True))
|
|
@@ -73,7 +63,7 @@ def load_config_files(
|
|
|
73
63
|
"--header",
|
|
74
64
|
multiple=True,
|
|
75
65
|
type=HeaderType(),
|
|
76
|
-
help="Headers to pass in the HTTP requests. Can be passed multiple times.",
|
|
66
|
+
help="Headers to pass in the HTTP requests. Can be passed multiple times. Other users of the machine can see the command line arguments, so pass the secrets with the SCIM_CLI_HEADERS environment variable.",
|
|
77
67
|
envvar="SCIM_CLI_HEADERS",
|
|
78
68
|
)
|
|
79
69
|
@click.option(
|
|
@@ -122,29 +112,21 @@ def cli(
|
|
|
122
112
|
headers_dict = split_headers(header)
|
|
123
113
|
client = Client(base_url=url, headers=headers_dict, verify=not no_verify)
|
|
124
114
|
|
|
125
|
-
|
|
126
|
-
schemas, resource_types, service_provider_config
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
scim_client = SyncSCIMClient(
|
|
130
|
-
client,
|
|
131
|
-
resource_models=resource_models,
|
|
132
|
-
resource_types=resource_types_obj,
|
|
133
|
-
service_provider_config=spc_obj,
|
|
134
|
-
)
|
|
115
|
+
scim_client = SyncSCIMClient(client)
|
|
135
116
|
try:
|
|
136
|
-
scim_client.
|
|
137
|
-
|
|
138
|
-
resource_types=not bool(resource_types),
|
|
139
|
-
service_provider_config=not bool(service_provider_config),
|
|
117
|
+
scim_client.provider = describe_server(
|
|
118
|
+
scim_client, schemas, resource_types, service_provider_config
|
|
140
119
|
)
|
|
141
|
-
except
|
|
120
|
+
except (*SCIM_EXCEPTIONS, ScimProviderError) as exc:
|
|
142
121
|
raise exception_to_click_error(exc) from exc
|
|
143
122
|
|
|
123
|
+
provider = scim_client.provider
|
|
144
124
|
ctx.obj["client"] = scim_client
|
|
145
125
|
ctx.obj["resource_models"] = {
|
|
146
|
-
|
|
147
|
-
|
|
126
|
+
escape_control_characters(
|
|
127
|
+
re.sub(r"\[.*\]", "", resource_model.__name__.lower())
|
|
128
|
+
): resource_model
|
|
129
|
+
for resource_model in map(provider.model_for, provider.resource_types)
|
|
148
130
|
}
|
|
149
131
|
|
|
150
132
|
if not click.get_text_stream("stdin").isatty(): # pragma: no cover
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from click import ClickException
|
|
3
3
|
from pydanclick import from_pydantic
|
|
4
|
-
from scim2_client import SCIMClientError
|
|
5
4
|
from scim2_models import Context
|
|
6
5
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
7
6
|
|
|
8
7
|
from scim2_cli.utils import DOC_URL
|
|
8
|
+
from scim2_cli.utils import SCIM_EXCEPTIONS
|
|
9
9
|
from scim2_cli.utils import ModelCommand
|
|
10
|
+
from scim2_cli.utils import escape_options_help
|
|
10
11
|
from scim2_cli.utils import exception_to_click_error
|
|
11
12
|
from scim2_cli.utils import formatted_payload
|
|
12
13
|
from scim2_cli.utils import unacceptable_fields
|
|
@@ -16,7 +17,7 @@ def create_payload(client, payload, indent):
|
|
|
16
17
|
try:
|
|
17
18
|
response = client.create(payload, raise_scim_errors=False)
|
|
18
19
|
|
|
19
|
-
except
|
|
20
|
+
except SCIM_EXCEPTIONS as scim_exc:
|
|
20
21
|
raise exception_to_click_error(scim_exc) from scim_exc
|
|
21
22
|
|
|
22
23
|
payload = formatted_payload(response.model_dump(), indent)
|
|
@@ -75,7 +76,7 @@ def create_factory(model):
|
|
|
75
76
|
|
|
76
77
|
create_payload(ctx.obj["client"], payload, indent)
|
|
77
78
|
|
|
78
|
-
return create_command
|
|
79
|
+
return escape_options_help(create_command)
|
|
79
80
|
|
|
80
81
|
|
|
81
82
|
@click.command(
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from click import ClickException
|
|
3
|
-
from scim2_client import SCIMClientError
|
|
4
3
|
from scim2_models import Message
|
|
5
4
|
from scim2_models import Resource
|
|
6
5
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
@@ -8,6 +7,7 @@ from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
|
8
7
|
from scim2_cli.utils import exception_to_click_error
|
|
9
8
|
|
|
10
9
|
from .utils import DOC_URL
|
|
10
|
+
from .utils import SCIM_EXCEPTIONS
|
|
11
11
|
from .utils import formatted_payload
|
|
12
12
|
|
|
13
13
|
|
|
@@ -39,7 +39,7 @@ def delete_cli(ctx, resource_type, id, indent):
|
|
|
39
39
|
try:
|
|
40
40
|
response = ctx.obj["client"].delete(resource_model, id, raise_scim_errors=False)
|
|
41
41
|
|
|
42
|
-
except
|
|
42
|
+
except SCIM_EXCEPTIONS as scim_exc:
|
|
43
43
|
raise exception_to_click_error(scim_exc) from scim_exc
|
|
44
44
|
|
|
45
45
|
if response:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from click import ClickException
|
|
3
|
-
from scim2_client import SCIMClientError
|
|
4
3
|
from scim2_models import ResourceType
|
|
4
|
+
from scim2_models import ResponseParameters
|
|
5
5
|
from scim2_models import Schema
|
|
6
6
|
from scim2_models import SearchRequest
|
|
7
7
|
from scim2_models import ServiceProviderConfig
|
|
@@ -10,6 +10,7 @@ from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
|
10
10
|
from scim2_cli.utils import exception_to_click_error
|
|
11
11
|
|
|
12
12
|
from .utils import DOC_URL
|
|
13
|
+
from .utils import SCIM_EXCEPTIONS
|
|
13
14
|
from .utils import formatted_payload
|
|
14
15
|
|
|
15
16
|
|
|
@@ -73,6 +74,9 @@ def query_cli(
|
|
|
73
74
|
- If :code:`RESOURCE_TYPE` is :code:`user` and :code:`id` is not set, then the request will made on the :code:`/Users` endpoint.
|
|
74
75
|
- If :code:`RESOURCE_TYPE` is not set, then the request will made on the :code:`/` endpoint.
|
|
75
76
|
|
|
77
|
+
When a single resource is queried, only :code:`--attribute` and :code:`--excluded-attribute` are
|
|
78
|
+
available, as defined in `RFC7644 §3.4.1 <https://www.rfc-editor.org/rfc/rfc7644#section-3.4.1>`_.
|
|
79
|
+
|
|
76
80
|
Data passed in JSON format to stdin is sent as request arguments and all the other query arguments are ignored:
|
|
77
81
|
|
|
78
82
|
.. code-block:: bash
|
|
@@ -92,10 +96,34 @@ def query_cli(
|
|
|
92
96
|
f"Unknown resource type '{resource_type}. Available values are: {ok_values}'"
|
|
93
97
|
) from exc
|
|
94
98
|
|
|
99
|
+
single_resource = bool(id) or resource_type is ServiceProviderConfig
|
|
100
|
+
listing_options = [
|
|
101
|
+
name
|
|
102
|
+
for name, value in (
|
|
103
|
+
("--start-index", start_index),
|
|
104
|
+
("--count", count),
|
|
105
|
+
("--filter", filter),
|
|
106
|
+
("--sort-by", sort_by),
|
|
107
|
+
("--sort-order", sort_order),
|
|
108
|
+
)
|
|
109
|
+
if value is not None
|
|
110
|
+
]
|
|
111
|
+
if single_resource and listing_options:
|
|
112
|
+
raise ClickException(
|
|
113
|
+
f"{', '.join(listing_options)} cannot be used when querying a single resource."
|
|
114
|
+
)
|
|
115
|
+
|
|
95
116
|
if ctx.obj.get("stdin"):
|
|
96
117
|
check_request_payload = False
|
|
97
118
|
payload = ctx.obj.get("stdin")
|
|
98
119
|
|
|
120
|
+
elif single_resource:
|
|
121
|
+
check_request_payload = True
|
|
122
|
+
payload = ResponseParameters(
|
|
123
|
+
attributes=attribute,
|
|
124
|
+
excluded_attributes=excluded_attribute,
|
|
125
|
+
)
|
|
126
|
+
|
|
99
127
|
else:
|
|
100
128
|
check_request_payload = True
|
|
101
129
|
payload = SearchRequest(
|
|
@@ -112,12 +140,12 @@ def query_cli(
|
|
|
112
140
|
response = ctx.obj["client"].query(
|
|
113
141
|
resource_type,
|
|
114
142
|
id,
|
|
115
|
-
|
|
143
|
+
query_parameters=payload,
|
|
116
144
|
check_request_payload=check_request_payload,
|
|
117
145
|
raise_scim_errors=False,
|
|
118
146
|
)
|
|
119
147
|
|
|
120
|
-
except
|
|
148
|
+
except SCIM_EXCEPTIONS as scim_exc:
|
|
121
149
|
raise exception_to_click_error(scim_exc) from scim_exc
|
|
122
150
|
|
|
123
151
|
payload = formatted_payload(response.model_dump(), indent)
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from click import ClickException
|
|
3
3
|
from pydanclick import from_pydantic
|
|
4
|
-
from scim2_client import SCIMClientError
|
|
5
4
|
from scim2_models import Context
|
|
6
5
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
7
6
|
|
|
7
|
+
from scim2_cli.utils import escape_options_help
|
|
8
8
|
from scim2_cli.utils import exception_to_click_error
|
|
9
9
|
|
|
10
10
|
from .utils import DOC_URL
|
|
11
|
+
from .utils import SCIM_EXCEPTIONS
|
|
11
12
|
from .utils import ModelCommand
|
|
12
13
|
from .utils import formatted_payload
|
|
13
14
|
from .utils import unacceptable_fields
|
|
@@ -17,7 +18,7 @@ def replace_payload(client, payload, indent):
|
|
|
17
18
|
try:
|
|
18
19
|
response = client.replace(payload, raise_scim_errors=False)
|
|
19
20
|
|
|
20
|
-
except
|
|
21
|
+
except SCIM_EXCEPTIONS as scim_exc:
|
|
21
22
|
raise exception_to_click_error(scim_exc) from scim_exc
|
|
22
23
|
|
|
23
24
|
payload = formatted_payload(response.model_dump(), indent)
|
|
@@ -82,7 +83,7 @@ def replace_factory(model):
|
|
|
82
83
|
indent,
|
|
83
84
|
)
|
|
84
85
|
|
|
85
|
-
return replace_command
|
|
86
|
+
return escape_options_help(replace_command)
|
|
86
87
|
|
|
87
88
|
|
|
88
89
|
@click.command(
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import click
|
|
2
|
-
from scim2_client import SCIMClientError
|
|
3
2
|
from scim2_models import SearchRequest
|
|
4
3
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
5
4
|
|
|
6
5
|
from scim2_cli.utils import exception_to_click_error
|
|
7
6
|
|
|
8
7
|
from .utils import DOC_URL
|
|
8
|
+
from .utils import SCIM_EXCEPTIONS
|
|
9
9
|
from .utils import formatted_payload
|
|
10
10
|
|
|
11
11
|
|
|
@@ -91,7 +91,7 @@ def search_cli(
|
|
|
91
91
|
raise_scim_errors=False,
|
|
92
92
|
)
|
|
93
93
|
|
|
94
|
-
except
|
|
94
|
+
except SCIM_EXCEPTIONS as scim_exc:
|
|
95
95
|
raise exception_to_click_error(scim_exc) from scim_exc
|
|
96
96
|
|
|
97
97
|
payload = formatted_payload(response.model_dump(), indent)
|
|
@@ -7,6 +7,7 @@ from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
|
7
7
|
|
|
8
8
|
from .utils import DOC_URL
|
|
9
9
|
from .utils import Color
|
|
10
|
+
from .utils import escape_control_characters
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="test")
|
|
@@ -32,8 +33,8 @@ def test_cli(ctx, verbose, check_status_code, check_content_type):
|
|
|
32
33
|
test
|
|
33
34
|
"""
|
|
34
35
|
client = ctx.obj["client"]
|
|
35
|
-
client.
|
|
36
|
-
client.
|
|
36
|
+
client.check_response_status_codes = check_status_code
|
|
37
|
+
client.check_response_content_type = check_content_type
|
|
37
38
|
results = check_server(client)
|
|
38
39
|
click.echo(f"Performing a SCIM compliance check on {client.client.base_url} ...")
|
|
39
40
|
success = True
|
|
@@ -43,11 +44,11 @@ def test_cli(ctx, verbose, check_status_code, check_content_type):
|
|
|
43
44
|
else:
|
|
44
45
|
status = click.style(result.status.name, fg=Color.red)
|
|
45
46
|
success = False
|
|
46
|
-
click.echo(f"{status} {result.title}")
|
|
47
|
+
click.echo(f"{status} {escape_control_characters(result.title)}")
|
|
47
48
|
|
|
48
49
|
if result.reason:
|
|
49
|
-
click.echo(f" {result.reason}")
|
|
50
|
+
click.echo(f" {escape_control_characters(result.reason)}")
|
|
50
51
|
if verbose and result.data:
|
|
51
|
-
click.echo(f" {result.data}")
|
|
52
|
+
click.echo(f" {escape_control_characters(result.data)}")
|
|
52
53
|
|
|
53
54
|
sys.exit(0 if success else 1)
|
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import
|
|
3
|
-
from enum import
|
|
2
|
+
import re
|
|
3
|
+
from enum import StrEnum
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
|
+
from scim2_client import SCIMClientException
|
|
7
|
+
from scim2_models import SCIMException
|
|
6
8
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
7
9
|
|
|
8
10
|
DOC_URL = "https://scim2-cli.readthedocs.io/"
|
|
9
11
|
INDENTATION_SIZE = 4
|
|
10
12
|
|
|
13
|
+
SCIM_EXCEPTIONS = (SCIMClientException, SCIMException)
|
|
14
|
+
|
|
15
|
+
CONTROL_CHARACTERS = re.compile(
|
|
16
|
+
"[\x00-\x08\x0b-\x1f\x7f-\x9f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]"
|
|
17
|
+
)
|
|
18
|
+
|
|
11
19
|
|
|
12
20
|
class HeaderType(click.ParamType):
|
|
13
21
|
envvar_list_splitter = ";"
|
|
14
22
|
name = "HEADER"
|
|
15
23
|
|
|
16
24
|
|
|
17
|
-
class Color(
|
|
25
|
+
class Color(StrEnum):
|
|
18
26
|
black = "black"
|
|
19
27
|
red = "red"
|
|
20
28
|
green = "green"
|
|
@@ -33,6 +41,25 @@ class Color(str, Enum):
|
|
|
33
41
|
bright_white = "bright_white"
|
|
34
42
|
|
|
35
43
|
|
|
44
|
+
def escape_control_characters(text) -> str:
|
|
45
|
+
"""Replace the control characters of a text by their escaped form.
|
|
46
|
+
|
|
47
|
+
The texts coming from the server would otherwise be interpreted by the
|
|
48
|
+
terminal as escape sequences.
|
|
49
|
+
"""
|
|
50
|
+
return CONTROL_CHARACTERS.sub(
|
|
51
|
+
lambda match: match.group().encode("unicode_escape").decode(), str(text)
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def escape_options_help(command):
|
|
56
|
+
"""Escape the option help texts pydanclick takes from the schema descriptions."""
|
|
57
|
+
for param in command.params:
|
|
58
|
+
if param.help:
|
|
59
|
+
param.help = escape_control_characters(param.help)
|
|
60
|
+
return command
|
|
61
|
+
|
|
62
|
+
|
|
36
63
|
def formatted_payload(obj, indent):
|
|
37
64
|
indent = INDENTATION_SIZE if indent else False
|
|
38
65
|
return json.dumps(obj, indent=indent)
|
|
@@ -111,8 +138,6 @@ def unacceptable_fields(context, model):
|
|
|
111
138
|
|
|
112
139
|
def exception_to_click_error(exception):
|
|
113
140
|
message = str(exception)
|
|
114
|
-
if
|
|
115
|
-
exception, "__notes__"
|
|
116
|
-
): # pragma: no cover
|
|
141
|
+
if hasattr(exception, "__notes__"):
|
|
117
142
|
message += "\n" + "\n".join(exception.__notes__)
|
|
118
|
-
return click.ClickException(message)
|
|
143
|
+
return click.ClickException(escape_control_characters(message))
|
|
File without changes
|
|
File without changes
|