Skip to content

Commit a19fe07

Browse files
committed
Extend ExecResult API
* property `ok` * methods `check_exit_code` and `raise_for_status`
1 parent a927d9e commit a19fe07

File tree

5 files changed

+104
-82
lines changed

5 files changed

+104
-82
lines changed

.pylintrc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ limit-inference-results=100
3131
load-plugins=pylint.extensions.docparams,
3232
pylint.extensions.docstyle,
3333
pylint.extensions.overlapping_exceptions,
34-
pylint.extensions.emptystring,
35-
pylint.extensions.comparetozero,
3634
pylint.extensions.check_elif,
3735
pylint.extensions.for_any_all,
3836
pylint.extensions.code_style,
@@ -90,6 +88,8 @@ disable=locally-disabled,
9088
broad-except,
9189
logging-fstring-interpolation,
9290
logging-format-interpolation,
91+
consider-alternative-union-syntax,
92+
deprecated-typing-alias,
9393
invalid-name
9494

9595
# Enable the message, report, category or checker with the given id(s). You can

doc/source/ExecResult.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,38 @@ API: ExecResult
126126

127127
:rtype: int | ExitCodes
128128

129+
.. py:attribute:: ok
130+
131+
``bool``
132+
133+
Exit code is EX_OK
134+
135+
.. py:method:: check_exit_code(expected_codes=(0,), raise_on_err=True, error_info=None, exception_class=CalledProcessError, logger=LOGGER)
136+
137+
Check exit code and log/raise for unexpected code.
138+
139+
:param error_info: optional additional error information
140+
:type error_info: str | None
141+
:param raise_on_err: raise `exception_class` in case of error
142+
:type raise_on_err: bool
143+
:param expected_codes: iterable expected exit codes
144+
:type expected_codes: Iterable[int | ExitCodes]
145+
:param exception_class: exception class for usage in case of errors (subclass of CalledProcessError)
146+
:type exception_class: type[exceptions.CalledProcessError]
147+
:param logger: logger instance for error log
148+
:type logger: logging.Logger
149+
:raises exceptions.CalledProcessError: unexpected exit code and raise_on_err enabled
150+
151+
.. py:method:: raise_for_status(expected_codes=(0,), exception_class=CalledProcessError)
152+
153+
Requests-like exit code checker.
154+
155+
:param expected_codes: iterable expected exit codes
156+
:type expected_codes: Iterable[int | ExitCodes]
157+
:param exception_class: exception class for usage in case of errors (subclass of CalledProcessError)
158+
:type exception_class: type[exceptions.CalledProcessError]
159+
:raises exceptions.CalledProcessError: unexpected exit code and raise_on_err enabled
160+
129161
.. py:attribute:: started
130162
131163
``datetime.datetime``

exec_helpers/api.py

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ def execute(
527527
self.logger.log(level=log_level, msg=f"Command {result.cmd!r} exit code: {result.exit_code!s}")
528528
return result
529529

530-
def __call__( # pylint: disable=arguments-differ
530+
def __call__(
531531
self,
532532
command: CommandT,
533533
verbose: bool = False,
@@ -658,48 +658,13 @@ def check_call(
658658
log_stderr=log_stderr,
659659
**kwargs,
660660
)
661-
return self._handle_exit_code(
662-
result=result,
661+
result.check_exit_code(
662+
expected_codes,
663+
raise_on_err,
663664
error_info=error_info,
664-
expected_codes=expected_codes,
665-
raise_on_err=raise_on_err,
666665
exception_class=exception_class,
666+
logger=self.logger,
667667
)
668-
669-
def _handle_exit_code(
670-
self,
671-
*,
672-
result: exec_result.ExecResult,
673-
error_info: ErrorInfoT,
674-
expected_codes: ExpectedExitCodesT,
675-
raise_on_err: bool,
676-
exception_class: CalledProcessErrorSubClassT,
677-
) -> exec_result.ExecResult:
678-
"""Internal check_call logic (synchronous).
679-
680-
:param result: execution result for validation
681-
:type result: exec_result.ExecResult
682-
:param error_info: optional additional error information
683-
:type error_info: str | None
684-
:param raise_on_err: raise `exception_class` in case of error
685-
:type raise_on_err: bool
686-
:param expected_codes: iterable expected exit codes
687-
:type expected_codes: Iterable[int | ExitCodes]
688-
:param exception_class: exception class for usage in case of errors (subclass of CalledProcessError)
689-
:type exception_class: type[exceptions.CalledProcessError]
690-
:return: execution result
691-
:rtype: exec_result.ExecResult
692-
:raises exceptions.CalledProcessError: stderr presents and raise_on_err enabled
693-
"""
694-
append: str = error_info + "\n" if error_info else ""
695-
if result.exit_code not in expected_codes:
696-
message = (
697-
f"{append}Command {result.cmd!r} returned exit code {result.exit_code!s} "
698-
f"while expected {expected_codes!s}"
699-
)
700-
self.logger.error(msg=message)
701-
if raise_on_err:
702-
raise exception_class(result=result, expected=expected_codes)
703668
return result
704669

705670
def check_stderr(

exec_helpers/async_api/api.py

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ async def execute(
481481
self.logger.log(level=log_level, msg=f"Command {result.cmd!r} exit code: {result.exit_code!s}")
482482
return result
483483

484-
async def __call__( # pylint: disable=invalid-overridden-method,arguments-differ
484+
async def __call__( # pylint: disable=invalid-overridden-method
485485
self,
486486
command: CommandT,
487487
verbose: bool = False,
@@ -610,48 +610,13 @@ async def check_call(
610610
log_stderr=log_stderr,
611611
**kwargs,
612612
)
613-
return self._handle_exit_code(
614-
result=result,
613+
result.check_exit_code(
614+
expected_codes,
615+
raise_on_err,
615616
error_info=error_info,
616-
expected_codes=expected_codes,
617-
raise_on_err=raise_on_err,
618617
exception_class=exception_class,
618+
logger=self.logger,
619619
)
620-
621-
def _handle_exit_code(
622-
self,
623-
*,
624-
result: exec_result.ExecResult,
625-
error_info: ErrorInfoT,
626-
expected_codes: ExpectedExitCodesT,
627-
raise_on_err: bool,
628-
exception_class: CalledProcessErrorSubClassT,
629-
) -> exec_result.ExecResult:
630-
"""Internal check_call logic (synchronous).
631-
632-
:param result: execution result for validation
633-
:type result: exec_result.ExecResult
634-
:param error_info: optional additional error information
635-
:type error_info: str | None
636-
:param raise_on_err: raise `exception_class` in case of error
637-
:type raise_on_err: bool
638-
:param expected_codes: iterable expected exit codes
639-
:type expected_codes: Iterable[int | ExitCodes]
640-
:param exception_class: exception class for usage in case of errors (subclass of CalledProcessError)
641-
:type exception_class: type[exceptions.CalledProcessError]
642-
:return: execution result
643-
:rtype: exec_result.ExecResult
644-
:raises exceptions.CalledProcessError: stderr presents and raise_on_err enabled
645-
"""
646-
append: str = error_info + "\n" if error_info else ""
647-
if result.exit_code not in expected_codes:
648-
message = (
649-
f"{append}Command {result.cmd!r} returned exit code {result.exit_code!s} "
650-
f"while expected {expected_codes!s}"
651-
)
652-
self.logger.error(msg=message)
653-
if raise_on_err:
654-
raise exception_class(result=result, expected=expected_codes)
655620
return result
656621

657622
async def check_stderr(

exec_helpers/exec_result.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
if typing.TYPE_CHECKING:
5353
import xml.etree.ElementTree # nosec # for typing only
5454
from collections.abc import Callable
55+
from collections.abc import Collection
5556
from collections.abc import Iterable
5657
from collections.abc import Sequence
5758

@@ -564,6 +565,65 @@ def exit_code(self, new_val: ExitCodeT) -> None:
564565
if self.__exit_code != proc_enums.INVALID:
565566
self.__timestamp = datetime.datetime.now(tz=datetime.timezone.utc)
566567

568+
@property
569+
def ok(self) -> bool:
570+
"""Exit code is EX_OK.
571+
572+
:return: Exit code is EX_OK
573+
:rtype: bool
574+
"""
575+
return self.exit_code == 0 # pylint: disable=use-implicit-booleaness-not-comparison-to-zero
576+
577+
def check_exit_code(
578+
self,
579+
expected_codes: Iterable[ExitCodeT] = (0,),
580+
raise_on_err: bool = True,
581+
*,
582+
error_info: str | None = None,
583+
exception_class: type[exceptions.CalledProcessError] = exceptions.CalledProcessError,
584+
logger: logging.Logger = LOGGER,
585+
) -> None:
586+
"""Check exit code and log/raise for unexpected code.
587+
588+
:param error_info: optional additional error information
589+
:type error_info: str | None
590+
:param raise_on_err: raise `exception_class` in case of error
591+
:type raise_on_err: bool
592+
:param expected_codes: iterable expected exit codes
593+
:type expected_codes: Iterable[int | ExitCodes]
594+
:param exception_class: exception class for usage in case of errors (subclass of CalledProcessError)
595+
:type exception_class: type[exceptions.CalledProcessError]
596+
:param logger: logger instance for error log
597+
:type logger: logging.Logger
598+
:raises exceptions.CalledProcessError: unexpected exit code and raise_on_err enabled
599+
"""
600+
append: str = error_info + "\n" if error_info else ""
601+
expected = tuple(frozenset(expected_codes))
602+
if self.exit_code not in expected:
603+
message = f"{append}Command {self.cmd!r} returned exit code {self.exit_code!s} while expected {expected!s}"
604+
logger.error(msg=message)
605+
if raise_on_err:
606+
self.raise_for_status(expected_codes=expected, exception_class=exception_class)
607+
608+
def raise_for_status(
609+
self,
610+
expected_codes: Collection[ExitCodeT] = (0,),
611+
*,
612+
exception_class: type[exceptions.CalledProcessError] = exceptions.CalledProcessError,
613+
) -> None:
614+
"""Requests-like exit code checker.
615+
616+
:param expected_codes: iterable expected exit codes
617+
:type expected_codes: Iterable[int | ExitCodes]
618+
:param exception_class: exception class for usage in case of errors (subclass of CalledProcessError)
619+
:type exception_class: type[exceptions.CalledProcessError]
620+
:raises exceptions.CalledProcessError: unexpected exit code and raise_on_err enabled
621+
"""
622+
if self.exit_code in expected_codes:
623+
return
624+
625+
raise exception_class(self, expected_codes)
626+
567627
@property
568628
def started(self) -> datetime.datetime | None:
569629
"""Timestamp of command start.

0 commit comments

Comments
 (0)