OJFord
OJFord

Reputation: 11140

How should a distutils `Command` return a non-zero exit code?

Consider the simplest possible python setup.py cmd:

from distutils.core import Command, setup


class Foo(Command):
    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        pass

setup(
    cmdclass={'cmd': Foo}
)

It does precisely nothing.

But suppose something goes wrong while we're doing nothing, how should the command return a non-zero exit code to the user?

Return values seem to be ignored; the documentation says only:

All terminal output and filesystem interaction should be done by run().

which seems relevant without being particularly helpful.

Perhaps we should infer to exit:

class Foo(Command):
    # ...
    def run(self):
        exit(1)

This works, of course.

But it's not clear to me that it's the right thing to do: if the command is run as a part of a longer process, or it's overriding a built-in command, presumably nothing further will execute.

We could raise a relevant exception directly instead, assuming it might be more likely to be well-handled, but then there's a nasty traceback when we exit - what if we've already logged something nicer ourselves?

class Foo(Command):
    # ...
    def run(self):
        print('Oh noes!', file=sys.stderr)
        exit(1)

Is this safe to do; is there a better alternative?

Upvotes: 3

Views: 579

Answers (1)

metatoaster
metatoaster

Reputation: 18938

While exit will defintely work, raising any of the distutils.errors with the error message desired to halt execution is how distutils handle problems internally. For instance, if one wish to prevent editable (develop) installation method provided by setuptools, using only distutils API, raising DistutilsError can be achieve this.

from setuptools.command.develop import develop 
from distutils.errors import DistutilsError

class fail_develop(develop):

    def run(self):
        raise DistutilsError('develop installation mode unsupported')

setup(
    ...
    cmdclass={'develop': fail_develop},
)

Running that with the develop option may result in this:

running develop
error: develop installation mode unsupported

Execution will halt, as that is trapped and a SystemExit will be raised (now if somehow the whole process is wrapped by some other Python libraries that trap all exceptions including SystemExit or some other process expect a non-zero exit code, a traceback or other unexpected output will likely be produced). While on the surface this is not too different than raising SystemExit (or calling sys.exit) directly, there are prefixes that do get generated for errors such as DistutilsSetupError that one might raise in a custom Distribution class. So with all that said, calling sys.exit is fine.

Upvotes: 1

Related Questions