Skip to content

Commit f02fb9d

Browse files
authored
Merge pull request #27 from python-scim/2-exit-codes
Fail with the exit code 1 when the server answers an error
2 parents 9cb5996 + dee0bfa commit f02fb9d

16 files changed

Lines changed: 209 additions & 34 deletions

‎doc/changelog.rst‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ Added
1616

1717
Changed
1818
^^^^^^^
19+
- The commands fail with the exit code 1 when the server answers an error, or a bulk response
20+
with failed operations. The response is still displayed on stdout, and a summary of the error
21+
is displayed on stderr. :issue:`2`
1922
- The errors of the server discovery give the URL of the server.
2023

2124
Fixed

‎doc/tutorial.rst‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,29 @@ See the :doc:`scim2-tester documentation <scim2_tester:index>` for more details
256256
SUCCESS check_access_invalid_resource_typ
257257
...
258258
259+
Errors
260+
------
261+
262+
When the server answers an error, the command displays it on stdout and fails with the exit code 1.
263+
A summary of the error is displayed on stderr, so the output can still be read by another program.
264+
265+
.. code-block:: console
266+
:caption: Querying an user that does not exist.
267+
268+
$ scim query user unknown-id
269+
{
270+
"schemas": [
271+
"urn:ietf:params:scim:api:messages:2.0:Error"
272+
],
273+
"status": "404",
274+
"detail": "Resource unknown-id not found"
275+
}
276+
Error: 404 Resource unknown-id not found
277+
$ echo $?
278+
1
279+
280+
The :ref:`reference:bulk` command fails the same way when some operations of the bulk response failed.
281+
259282
JSON input
260283
----------
261284

‎scim2_cli/bulk.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
from scim2_cli.utils import DOC_URL
55
from scim2_cli.utils import SCIM_EXCEPTIONS
6+
from scim2_cli.utils import echo_response
67
from scim2_cli.utils import exception_to_click_error
7-
from scim2_cli.utils import formatted_payload
88
from scim2_cli.utils import indent_option
99

1010

@@ -33,4 +33,4 @@ def bulk_cli(ctx: click.Context, indent: bool) -> None:
3333
except SCIM_EXCEPTIONS as scim_exc:
3434
raise exception_to_click_error(scim_exc) from scim_exc
3535

36-
click.echo(formatted_payload(response.model_dump(), indent))
36+
echo_response(response, indent)

‎scim2_cli/create.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
from scim2_cli.utils import SCIM_EXCEPTIONS
1616
from scim2_cli.utils import ModelCommand
1717
from scim2_cli.utils import command_name
18+
from scim2_cli.utils import echo_response
1819
from scim2_cli.utils import escape_options_help
1920
from scim2_cli.utils import exception_to_click_error
20-
from scim2_cli.utils import formatted_payload
2121
from scim2_cli.utils import indent_option
2222
from scim2_cli.utils import inherited
2323
from scim2_cli.utils import me_option
@@ -44,7 +44,7 @@ def create_payload(
4444
except SCIM_EXCEPTIONS as scim_exc:
4545
raise exception_to_click_error(scim_exc) from scim_exc
4646

47-
click.echo(formatted_payload(response.model_dump(), indent))
47+
echo_response(response, indent)
4848

4949

5050
def create_factory(

‎scim2_cli/delete.py‎

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import click
22
from click import ClickException
33
from scim2_client import Me
4-
from scim2_models import Message
5-
from scim2_models import Resource
64
from sphinx_click.rst_to_ansi_formatter import make_rst_to_ansi_formatter
75

86
from scim2_cli.utils import exception_to_click_error
@@ -11,7 +9,7 @@
119

1210
from .utils import DOC_URL
1311
from .utils import SCIM_EXCEPTIONS
14-
from .utils import formatted_payload
12+
from .utils import echo_response
1513

1614

1715
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="delete")
@@ -58,11 +56,4 @@ def delete_cli(
5856
except SCIM_EXCEPTIONS as scim_exc:
5957
raise exception_to_click_error(scim_exc) from scim_exc
6058

61-
if response:
62-
payload = (
63-
response.model_dump()
64-
if isinstance(response, Resource | Message)
65-
else response
66-
)
67-
payload = formatted_payload(payload, indent)
68-
click.echo(payload)
59+
echo_response(response, indent)

‎scim2_cli/modify.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818

1919
from scim2_cli.utils import DOC_URL
2020
from scim2_cli.utils import SCIM_EXCEPTIONS
21+
from scim2_cli.utils import echo_response
2122
from scim2_cli.utils import escape_control_characters
2223
from scim2_cli.utils import exception_to_click_error
2324
from scim2_cli.utils import find_target
24-
from scim2_cli.utils import formatted_payload
2525
from scim2_cli.utils import indent_option
2626
from scim2_cli.utils import me_option
2727

@@ -206,5 +206,4 @@ def modify_cli(
206206
except SCIM_EXCEPTIONS as scim_exc:
207207
raise exception_to_click_error(scim_exc) from scim_exc
208208

209-
if response is not None:
210-
click.echo(formatted_payload(response.model_dump(), indent))
209+
echo_response(response, indent)

‎scim2_cli/query.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from .utils import DOC_URL
1919
from .utils import SCIM_EXCEPTIONS
20-
from .utils import formatted_payload
20+
from .utils import echo_response
2121

2222
DISCOVERY_MODELS = (Schema, ResourceType, ServiceProviderConfig)
2323

@@ -169,5 +169,4 @@ def query_cli(
169169
except SCIM_EXCEPTIONS as scim_exc:
170170
raise exception_to_click_error(scim_exc) from scim_exc
171171

172-
payload = formatted_payload(response.model_dump(), indent)
173-
click.echo(payload)
172+
echo_response(response, indent)

‎scim2_cli/replace.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .utils import DOC_URL
2222
from .utils import SCIM_EXCEPTIONS
2323
from .utils import ModelCommand
24-
from .utils import formatted_payload
24+
from .utils import echo_response
2525
from .utils import renamed_fields
2626
from .utils import unacceptable_fields
2727

@@ -45,7 +45,7 @@ def replace_payload(
4545
except SCIM_EXCEPTIONS as scim_exc:
4646
raise exception_to_click_error(scim_exc) from scim_exc
4747

48-
click.echo(formatted_payload(response.model_dump(), indent))
48+
echo_response(response, indent)
4949

5050

5151
def replace_factory(

‎scim2_cli/search.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from .utils import DOC_URL
99
from .utils import SCIM_EXCEPTIONS
10-
from .utils import formatted_payload
10+
from .utils import echo_response
1111

1212

1313
@click.command(cls=make_rst_to_ansi_formatter(DOC_URL), name="search")
@@ -113,5 +113,4 @@ def search_cli(
113113
except SCIM_EXCEPTIONS as scim_exc:
114114
raise exception_to_click_error(scim_exc) from scim_exc
115115

116-
payload = formatted_payload(response.model_dump(), indent)
117-
click.echo(payload)
116+
echo_response(response, indent)

‎scim2_cli/utils.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from click.core import ParameterSource
1313
from scim2_client import SCIMClientException
1414
from scim2_models import BaseModel
15+
from scim2_models import BulkResponse
1516
from scim2_models import Context
17+
from scim2_models import Error
1618
from scim2_models import Mutability
1719
from scim2_models import Resource
1820
from scim2_models import ResourceType
@@ -129,6 +131,30 @@ def formatted_payload(obj: Any, indent: bool) -> str:
129131
return json.dumps(obj, indent=INDENTATION_SIZE if indent else None)
130132

131133

134+
def echo_response(response: Any, indent: bool) -> None:
135+
"""Display the response of the server, and exit with an error code if it reports a failure."""
136+
if response is None:
137+
return
138+
139+
click.echo(formatted_payload(response.model_dump(), indent))
140+
if isinstance(response, Error):
141+
summary = " ".join(
142+
str(part) for part in (response.status, response.detail) if part
143+
)
144+
click.echo(f"Error: {escape_control_characters(summary)}", err=True)
145+
click.get_current_context().exit(1)
146+
147+
if isinstance(response, BulkResponse):
148+
operations = response.operations or []
149+
failed = [op for op in operations if op.status and op.status >= 400]
150+
if failed:
151+
click.echo(
152+
f"Error: {len(failed)} of {len(operations)} bulk operations failed",
153+
err=True,
154+
)
155+
click.get_current_context().exit(1)
156+
157+
132158
def split_headers(headers: list[str]) -> dict[str, str]:
133159
"""Make a dict from header strings.
134160

0 commit comments

Comments
 (0)