Jun
26
2017

How to handle Python 3 Fabric exceptions with a Decorator

Python’s Fabric module is an easy and efficient way for task based automation.  I use it to automate the installation of VoIP phone systems on CentOS 6/7.  In a nut shell, Fabric executes shell commands over SSH.  It also can be used to automate local tasks.  The purpose of this article is to highlight the basic process of capturing Fabric exceptions(try/except) using a wrapper function called a decorator.  A few well placed decorators can save lot of time coding the same thing over and over(like capturing a specific error). 

Most of you in the know about Python/Fabric probably  will find my examples trivial, but I had to dig to find the Fabric exception environment variable.  I’m sure it’s somewhere on the Fabric site… but I gave up after 30 minutes.  So hopefully you will find this post useful either way!

If you need help installing Python3, pip3 and Fabric please refer to my previous post.

Goals of this Post:

  • create a simple Fabric function that creates a directory
  • create a Fabric exception
  • create Decorator for adding exception handling

Lets start with a basic fabfile.py: 

Lets run test_func.

[matt@mattcom1 exception_wrapper]$ fab test_func
[] Executing task ‘test_func’
[localhost] local: mkdir ~matt/git/savelono-projects/exception_wrapper/somedir

Done.

The first time it works with no problem.  However, since we now have a directory called “somedir” if we execute the script again it will abort and throw errors.

matt@mattcom1exception_wrapper]$ fab test_func
[] Executing task ‘test_func’
[localhost] local: mkdir ~matt/git/savelono-projects/exception_wrapper/somedir
mkdir: cannot create directory ‘/home/matt/git/savelono-projects/exception_wrapper/somedir’: File exists

Fatal error: local() encountered an error (return code 1) while executing ‘mkdir ~matt/git/savelono-projects/exception_wrapper/somedir’

Aborting.
Traceback (most recent call last):
File “/usr/lib64/python3.4/site-packages/fabric/main.py”, line 762, in main
*args, **kwargs
File “/usr/lib64/python3.4/site-packages/fabric/tasks.py”, line 385, in execute
multiprocessing
File “/usr/lib64/python3.4/site-packages/fabric/tasks.py”, line 275, in _execute
return task.run(*args, **kwargs)
File “/usr/lib64/python3.4/site-packages/fabric/tasks.py”, line 172, in run
return self.wrapped(*args, **kwargs)
File “/home/matt/git/savelono-projects/exception_wrapper/fabfile.py”, line 35, in test_func
local(“mkdir ~matt/git/savelono-projects/exception_wrapper/somedir”)
File “/usr/lib64/python3.4/site-packages/fabric/operations.py”, line 1244, in local
error(message=msg, stdout=out, stderr=err)
File “/usr/lib64/python3.4/site-packages/fabric/utils.py”, line 359, in error
return func(message)
File “/usr/lib64/python3.4/site-packages/fabric/utils.py”, line 55, in abort
raise env.abort_exception(msg)
fabfile.FabricException: local() encountered an error (return code 1) while executing ‘mkdir ~matt/git/savelono-projects/exception_wrapper/somedir’
[matt@mattcom1exception_wrapper]$

When we tried to create a directory the second time the script aborts.  This might be fine, but what if you are automating several Fabric functions at once?  The script could potentially exit before the rest of the Fabric tasks are executed.

So Let’e utilize ‘try’/’except’ and make a custom Fabric exception.  First add a empty class to handle exceptions.  Secondly the Fabric Enviroment variable ‘env.abort_exception’ is set to the name of your newly created exception class.

Secondly we will add a second function with ‘try’ & ‘except’ in place to capture the error.

Now lets test it!

[matt@mattcom1 exception_wrapper]$ fab test_func2
[] Executing task ‘test_func2’
[localhost] local: mkdir ~matt/git/savelono-projects/exception_wrapper/somedir
mkdir: cannot create directory ‘/home/matt/git/savelono-projects/exception_wrapper/somedir’: No such file or directory

Fatal error: local() encountered an error (return code 1) while executing ‘mkdir ~matt/git/savelono-projects/exception_wrapper/somedir’

Aborting.
directory already exists!!!

Done.
[matt@mattcom1 exception_wrapper]$

Now we have managed the error!  But what if I have many functions that need their own exceptions?  The solution is to write a type of wrapper called a decorator.  

Decorators are a special type of function that takes a function as input and returns a function as output.  Our decorator will add “try” & “except” statements to our function.

Decorators can be added to any existing compatible function by placing @name_of_decorator over the line defining the function.  Lets create a new function for making a directory and decorate it with ‘@try_to_except’.  This example is essentially func1 with the ‘@try_to_except’ decorator one line above it.

Lets run it!

[matt@mattcom1 exception_wrapper]$ fab test_func3
[] Executing task ‘test_func3’
[localhost] local: mkdir ~matt/git/savelono-projects/exception_wrapper/somedir
mkdir: cannot create directory ‘/home/matt/git/savelono-projects/exception_wrapper/somedir’: No such file or directory

Fatal error: local() encountered an error (return code 1) while executing ‘mkdir ~matt/git/savelono-projects/exception_wrapper/somedir’

Aborting.
directory already exists!!!

Done.
[matt@mattcom1 exception_wrapper]$

The result is exactly the same as func2’s result, but with fewer lines of code.  Now lets use the ‘@try_to_except’ on multiple different functions.

All three create a different directory, but use the same decorator(‘@try_to_except’ ) thereby reducing the amount of written code.

In conclusion, using Decorators for handling errors makes Fabric functions much more readable and faster to write.  Python really excels at getting the most out of every key stroke.  Perfect for those that love automation!

Here is the complete fabfile.py for this article:





No Comments »

RSS feed for comments on this post. TrackBack URL

Leave a comment