71

Normally Fabric quits as soon as a run() call returns a non-zero exit code. For some calls, however, this is expected. For example, PNGOut returns an error code of 2 when it is unable to compress a file.

Currently I can only circumvent this limitation by either using shell logic (do_something_that_fails || true or do_something_that_fails || do_something_else), but I'd rather be able to keep my logic in plain Python (as is the Fabric promise).

Is there a way to check for an error code and react to it rather than having Fabric panic and die? I still want the default behaviours for other calls, so changing its behaviour by modifying the environment doesn't seem like a good option (and as far as I recall, you can only use that to tell it to warn instead of dying anyway).

Alan Plum
  • 10,814
  • 4
  • 40
  • 57
  • 2
    Before someone marks this a duplicate: [this question is related](http://stackoverflow.com/questions/3876936/how-to-continue-the-task-when-fabric-meet-an-error), but as I said, I want to _react_ to errors, not ignore them. – Alan Plum Feb 03 '11 at 16:17
  • The currently accepted answer is dated. Since July 2013, there's been an environment variable that allows you to specify which exception should be raised when an error occurs (the default is a `SystemExit`, which isn't a descendant of Exception which is why it generally causes your program to crash). See my answer: http://stackoverflow.com/a/25293275/901641 – ArtOfWarfare Sep 15 '14 at 12:10
  • // , I'm curious how Invoke would handle this. – Nathan Basanese Oct 26 '16 at 00:44

4 Answers4

98

You can prevent aborting on non-zero exit codes by using the settings context manager and the warn_only setting:

from fabric.api import settings

with settings(warn_only=True):
    result = run('pngout old.png new.png')
    if result.return_code == 0: 
        do something
    elif result.return_code == 2: 
        do something else 
    else: #print error to user
        print result
        raise SystemExit()

Update: My answer is outdated. See comments below.

akaihola
  • 26,309
  • 7
  • 59
  • 69
  • 2
    You don't even need to check the return_code, the value of result evaluates as boolean true or false depending on the success state of the task.http://docs.fabfile.org/en/1.7/api/core/operations.html#fabric.operations.run – Fraser Graham Aug 12 '13 at 23:09
  • 3
    This doesn't work for timeout errors. With or without the "with settings" or even with a try/except, Fabric still exits completely. – Cerin Jul 16 '14 at 23:38
  • -1: This answer is dated - since July 2013 it's been possible to specify which exception is called when an error is called. See my answer here: http://stackoverflow.com/a/25293275/901641 – ArtOfWarfare Sep 15 '14 at 12:11
  • I fixed the Python indentation, but now I can't remove the extra text I added to get the edit to apply. – Christian Long Nov 11 '15 at 18:11
  • 3
    You can alse use `result.succeeded` and `result.failed`. Beware of evaluating directly `bool(result)` because it uses the output, so in conjunction with `hide("warnings")` and similar, will not be able to tell a success from a failure. – rewritten Jan 22 '16 at 10:27
29

Yes, you can. Just change the environment's abort_exception. For example:

from fabric.api import settings

class FabricException(Exception):
    pass

with settings(abort_exception = FabricException):
    try:
        run(<something that might fail>)
    except FabricException:
        <handle the exception>

The documentation on abort_exception is here.

ArtOfWarfare
  • 20,617
  • 19
  • 137
  • 193
4

Apparently messing with the environment is the answer.

fabric.api.settings can be used as a context manager (with with) to apply it to individual statements. The return value of run(), local() and sudo() calls isn't just the output of the shell command, but also has special properties (return_code and failed) that allow reacting to the errors.

I guess I was looking for something closer to the behaviour of subprocess.Popen or Python's usual exception handling.

Alan Plum
  • 10,814
  • 4
  • 40
  • 57
2

try this

from fabric.api import run, env
env.warn_only = True # if you want to ignore exceptions and handle them yurself

command = "your command"
x = run(command, capture=True) # run or local or sudo
if(x.stderr != ""):
    error = "On %s: %s" %(command, x.stderr)
    print error
    print x.return_code # which may be 1 or 2
    # do what you want or
    raise Exception(error) #optional
else:
    print "the output of %s is: %s" %(command, x)
    print x.return_code # which is 0
Yahya Yahyaoui
  • 2,833
  • 23
  • 30
  • 3
    I tried the above with the following result: `TypeError: run() got an unexpected keyword argument 'capture'` with Fabric 1.10.2 and Paramiko 1.15.2. – dmmfll Aug 18 '15 at 15:28
  • `capture=True` works only for `local(command, capture=True)` – Kurohige Oct 27 '20 at 10:16