diff --git a/CHANGES.rst b/CHANGES.rst index a41bfc618..da2c576f1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,8 @@ Version 7.0 (upcoming release with new features, release date to be decided) +- Non-standalone calls to Context.exit return the exit code, rather than + calling ``sys.exit`` (`#667`_)(`#533`_) - Updated test env matrix. (`#1027`_) - Fixes a ZeroDivisionError in ProgressBar.make_step, when the arg passed to the first call of ProgressBar.update is 0. (`#1012`_)(`#447`_) diff --git a/click/core.py b/click/core.py index c8e358443..2fa6e18e9 100644 --- a/click/core.py +++ b/click/core.py @@ -498,7 +498,7 @@ def abort(self): def exit(self, code=0): """Exits the application with a given exit code.""" - raise Exit(code, self) + raise Exit(code) def get_usage(self): """Helper method to get formatted usage string for the current @@ -714,16 +714,12 @@ def main(self, args=None, prog_name=None, complete_var=None, rv = self.invoke(ctx) if not standalone_mode: return rv - ctx.exit() + if not rv: + rv = 0 + ctx.exit(rv) except (EOFError, KeyboardInterrupt): echo(file=sys.stderr) raise Abort() - except Exit as e: - if not standalone_mode: - if e.exit_code: - raise - return None - sys.exit(e.exit_code) except ClickException as e: if not standalone_mode: raise @@ -734,6 +730,11 @@ def main(self, args=None, prog_name=None, complete_var=None, sys.exit(1) else: raise + except Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + return e.exit_code except Abort: if not standalone_mode: raise diff --git a/click/exceptions.py b/click/exceptions.py index b49aa6e3c..d8a9275cc 100644 --- a/click/exceptions.py +++ b/click/exceptions.py @@ -13,7 +13,6 @@ class ClickException(Exception): #: The exit code for this exception exit_code = 1 - show_prefix = 'Error: ' def __init__(self, message): ctor_msg = message @@ -38,17 +37,19 @@ def __str__(self): def show(self, file=None): if file is None: file = get_text_stderr() - echo('%s%s' % (self.show_prefix, self.format_message()), file=file) + echo('Error: %s' % self.format_message(), file=file) -class ContextAwareException(ClickException): - """An exception that knows how to use a :class:`~click.Context` object - to properly display itself. +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. :param message: the error message to display. :param ctx: optionally the context that caused this error. Click will fill in the context automatically in some situations. """ + exit_code = 2 + def __init__(self, message, ctx=None): ClickException.__init__(self, message) self.ctx = ctx @@ -66,33 +67,7 @@ def show(self, file=None): if self.ctx is not None: color = self.ctx.color echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color) - echo('%s%s' % (self.show_prefix, self.format_message()), file=file, color=color) - - -class Exit(ContextAwareException): - """An exception that indicates that the application should exit with some - status code. - - :param code: the status code to exit with. - :param ctx: optionally the context that caused this error. Click will - fill in the context automatically in some situations. - """ - show_prefix = 'Exit: ' - - def __init__(self, code, ctx=None): - ContextAwareException.__init__(self, str(code), ctx=None) - self.exit_code = code - - -class UsageError(ContextAwareException): - """An internal exception that signals a usage error. This typically - aborts any further handling. - - :param message: the error message to display. - :param ctx: optionally the context that caused this error. Click will - fill in the context automatically in some situations. - """ - exit_code = 2 + echo('Error: %s' % self.format_message(), file=file, color=color) class BadParameter(UsageError): @@ -248,3 +223,13 @@ def format_message(self): class Abort(RuntimeError): """An internal signalling exception that signals Click to abort.""" + + +class Exit(RuntimeError): + """An exception that indicates that the application should exit with some + status code. + + :param code: the status code to exit with. + """ + def __init__(self, code): + self.exit_code = code diff --git a/tests/test_context.py b/tests/test_context.py index cad2ae325..35933beba 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -import pytest - import click @@ -195,6 +193,7 @@ def test_close_before_pop(runner): @click.pass_context def cli(ctx): ctx.obj = 'test' + @ctx.call_on_close def foo(): assert click.get_current_context().obj == 'test' @@ -243,25 +242,17 @@ def test2(ctx, foo): assert result.output == 'foocmd\n' -def test_exit_not_standalone_failure(): +def test_exit_not_standalone(): @click.command() @click.pass_context def cli(ctx): ctx.exit(1) - with pytest.raises(click.exceptions.Exit) as e: - cli.main([], 'test_exit_not_standalone', standalone_mode=False) - assert e.value.exit_code == 1 - + assert cli.main([], 'test_exit_not_standalone', standalone_mode=False) == 1 -def test_exit_not_standalone_success(): @click.command() @click.pass_context def cli(ctx): ctx.exit(0) - assert cli.main( - [], - 'test_exit_not_standalone', - standalone_mode=False, - ) is None + assert cli.main([], 'test_exit_not_standalone', standalone_mode=False) == 0