scim2-cli 0.3.0__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.3.0 → scim2_cli-0.4.0}/PKG-INFO +4 -5
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/pyproject.toml +17 -8
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/pyproject.toml.orig +10 -8
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/scim2_cli/__init__.py +41 -59
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/scim2_cli/create.py +2 -1
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/scim2_cli/replace.py +2 -1
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/scim2_cli/test.py +4 -3
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/scim2_cli/utils.py +28 -7
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/LICENSE.md +0 -0
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/README.md +0 -0
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/scim2_cli/delete.py +0 -0
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/scim2_cli/py.typed +0 -0
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/scim2_cli/query.py +0 -0
- {scim2_cli-0.3.0 → scim2_cli-0.4.0}/scim2_cli/search.py +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,12 +218,12 @@ 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[httpx2]>=0.
|
|
223
|
-
Requires-Dist: scim2-tester>=0.
|
|
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
224
|
Requires-Dist: pydanclick>=0.5.0
|
|
226
225
|
Requires-Dist: pygments>=2.18.0
|
|
227
|
-
Requires-Python: >=3.
|
|
226
|
+
Requires-Python: >=3.11
|
|
228
227
|
Project-URL: documentation, https://scim2-cli.readthedocs.io
|
|
229
228
|
Project-URL: repository, https://github.com/python-scim/scim2-cli
|
|
230
229
|
Project-URL: changelog, https://scim2-cli.readthedocs.io/en/latest/changelog.html
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["uv_build>=0.12.
|
|
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
|
readme = "README.md"
|
|
10
10
|
keywords = [
|
|
@@ -17,7 +17,6 @@ keywords = [
|
|
|
17
17
|
classifiers = [
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
19
|
"Development Status :: 3 - Alpha",
|
|
20
|
-
"Programming Language :: Python :: 3.10",
|
|
21
20
|
"Programming Language :: Python :: 3.11",
|
|
22
21
|
"Programming Language :: Python :: 3.12",
|
|
23
22
|
"Programming Language :: Python :: 3.13",
|
|
@@ -28,11 +27,11 @@ classifiers = [
|
|
|
28
27
|
"Programming Language :: Python",
|
|
29
28
|
"Operating System :: OS Independent",
|
|
30
29
|
]
|
|
31
|
-
requires-python = ">= 3.
|
|
30
|
+
requires-python = ">= 3.11"
|
|
32
31
|
dependencies = [
|
|
33
32
|
"click>=8.1.7",
|
|
34
|
-
"scim2-client[httpx2]>=0.
|
|
35
|
-
"scim2-tester>=0.
|
|
33
|
+
"scim2-client[httpx2]>=0.9.0",
|
|
34
|
+
"scim2-tester>=0.4.0",
|
|
36
35
|
"sphinx-click-rst-to-ansi-formatter>=0.1.0",
|
|
37
36
|
"pydanclick>=0.5.0",
|
|
38
37
|
"pygments>=2.18.0",
|
|
@@ -60,7 +59,7 @@ dev = [
|
|
|
60
59
|
"mypy>=1.13.0",
|
|
61
60
|
"prek>=0.1.0",
|
|
62
61
|
"pytest>=8.2.1",
|
|
63
|
-
"pytest-
|
|
62
|
+
"pytest-cov>=6.0.0",
|
|
64
63
|
"pytest-httpserver>=1.0.10",
|
|
65
64
|
"tox-uv>=1.16.0",
|
|
66
65
|
]
|
|
@@ -76,6 +75,17 @@ doc = [
|
|
|
76
75
|
]
|
|
77
76
|
bundle = ["pyinstaller>=6.10.0"]
|
|
78
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
|
+
|
|
79
89
|
[tool.uv.build-backend]
|
|
80
90
|
module-root = ""
|
|
81
91
|
|
|
@@ -139,7 +149,6 @@ testpaths = ["tests"]
|
|
|
139
149
|
requires = ["tox>=4.19"]
|
|
140
150
|
env_list = [
|
|
141
151
|
"style",
|
|
142
|
-
"py310",
|
|
143
152
|
"py311",
|
|
144
153
|
"py312",
|
|
145
154
|
"py313",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["uv_build>=0.12.
|
|
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,11 +24,11 @@ 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[httpx2]>=0.
|
|
32
|
-
"scim2-tester>=0.
|
|
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
33
|
"pydanclick>=0.5.0",
|
|
35
34
|
"pygments>=2.18.0",
|
|
@@ -46,7 +45,7 @@ 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
|
]
|
|
@@ -68,6 +67,10 @@ bundle = [
|
|
|
68
67
|
scim = "scim2_cli:cli"
|
|
69
68
|
scim2 = "scim2_cli:cli"
|
|
70
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
|
+
|
|
71
74
|
[tool.uv.build-backend]
|
|
72
75
|
module-root = ""
|
|
73
76
|
|
|
@@ -133,7 +136,6 @@ testpaths = ["tests"]
|
|
|
133
136
|
requires = ["tox>=4.19"]
|
|
134
137
|
env_list = [
|
|
135
138
|
"style",
|
|
136
|
-
"py310",
|
|
137
139
|
"py311",
|
|
138
140
|
"py312",
|
|
139
141
|
"py313",
|
|
@@ -4,13 +4,12 @@ import re
|
|
|
4
4
|
import click
|
|
5
5
|
from httpx2 import Client
|
|
6
6
|
from scim2_client.engines.httpx2 import SyncSCIMClient
|
|
7
|
-
from scim2_models import Group
|
|
8
7
|
from scim2_models import ListResponse
|
|
9
|
-
from scim2_models import Resource
|
|
10
8
|
from scim2_models import ResourceType
|
|
11
9
|
from scim2_models import Schema
|
|
10
|
+
from scim2_models import ScimProvider
|
|
11
|
+
from scim2_models import ScimProviderError
|
|
12
12
|
from scim2_models import ServiceProviderConfig
|
|
13
|
-
from scim2_models import User
|
|
14
13
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
15
14
|
|
|
16
15
|
from scim2_cli.create import create_cli
|
|
@@ -22,48 +21,39 @@ from scim2_cli.test import test_cli
|
|
|
22
21
|
from scim2_cli.utils import DOC_URL
|
|
23
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 SCIM_EXCEPTIONS as exc:
|
|
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
|
|
@@ -7,6 +7,7 @@ from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
|
7
7
|
from scim2_cli.utils import DOC_URL
|
|
8
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
|
|
@@ -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(
|
|
@@ -4,6 +4,7 @@ from pydanclick import from_pydantic
|
|
|
4
4
|
from scim2_models import Context
|
|
5
5
|
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
|
|
6
6
|
|
|
7
|
+
from scim2_cli.utils import escape_options_help
|
|
7
8
|
from scim2_cli.utils import exception_to_click_error
|
|
8
9
|
|
|
9
10
|
from .utils import DOC_URL
|
|
@@ -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(
|
|
@@ -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")
|
|
@@ -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,6 +1,6 @@
|
|
|
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
6
|
from scim2_client import SCIMClientException
|
|
@@ -12,13 +12,17 @@ INDENTATION_SIZE = 4
|
|
|
12
12
|
|
|
13
13
|
SCIM_EXCEPTIONS = (SCIMClientException, SCIMException)
|
|
14
14
|
|
|
15
|
+
CONTROL_CHARACTERS = re.compile(
|
|
16
|
+
"[\x00-\x08\x0b-\x1f\x7f-\x9f\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]"
|
|
17
|
+
)
|
|
18
|
+
|
|
15
19
|
|
|
16
20
|
class HeaderType(click.ParamType):
|
|
17
21
|
envvar_list_splitter = ";"
|
|
18
22
|
name = "HEADER"
|
|
19
23
|
|
|
20
24
|
|
|
21
|
-
class Color(
|
|
25
|
+
class Color(StrEnum):
|
|
22
26
|
black = "black"
|
|
23
27
|
red = "red"
|
|
24
28
|
green = "green"
|
|
@@ -37,6 +41,25 @@ class Color(str, Enum):
|
|
|
37
41
|
bright_white = "bright_white"
|
|
38
42
|
|
|
39
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
|
+
|
|
40
63
|
def formatted_payload(obj, indent):
|
|
41
64
|
indent = INDENTATION_SIZE if indent else False
|
|
42
65
|
return json.dumps(obj, indent=indent)
|
|
@@ -115,8 +138,6 @@ def unacceptable_fields(context, model):
|
|
|
115
138
|
|
|
116
139
|
def exception_to_click_error(exception):
|
|
117
140
|
message = str(exception)
|
|
118
|
-
if
|
|
119
|
-
exception, "__notes__"
|
|
120
|
-
): # pragma: no cover
|
|
141
|
+
if hasattr(exception, "__notes__"):
|
|
121
142
|
message += "\n" + "\n".join(exception.__notes__)
|
|
122
|
-
return click.ClickException(message)
|
|
143
|
+
return click.ClickException(escape_control_characters(message))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|