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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#!/bin/python3 from fabric.api import * from fabric.contrib import files # Local git management automation tools # copyright Matt Birkland, Savelono.com 2017 # License: GPLv2 # # # Set the username env.user = "matt" # Set the password [NOT RECOMMENDED] env.password = "" env.hosts = "" def test_func(): local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir") |
Lets run 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.
[] 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.
1 2 3 4 5 6 7 8 9 |
# Generic Exception for capturing Fabric Errors #--------------------------------- class FabricException(Exception): pass env.abort_exception = FabricException #-------------------------------- |
Secondly we will add a second function with ‘try’ & ‘except’ in place to capture the error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# Test Function - Create a Directory on local host # # func1: errors out if a directory already exist at execution # func2: try & except capture error and return a message # the script continues # #============================================================ # 1. func1 def test_func1(): local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir") # 2. func2 def test_func2(): try: local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir") except FabricException: print("directory already exists!!!") |
Now lets test it!
[] 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.
1 2 3 4 5 6 7 8 9 10 11 12 |
# Exception Decorater #-------------------------------- def try_to_except(func): def newfunc(): try: func() except FabricException: print("directory already exists!!!") return newfunc |
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.
1 2 3 4 5 6 |
# 3. func3 @try_to_except def test_func3(): local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir") |
Lets run it!
[] 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 3. func3 @try_to_except def test_func3(): local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir") # 4. func4 make a folder called "somerdir2" @try_to_except def test_func4(): local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir2") # 5. func5 make a folder called "somerdir3" @try_to_except def test_func5(): local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir3") |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
#!/bin/python3 from fabric.api import * from fabric.contrib import files # Local git management automation tools # copyright matt birkland, Savelono.com 2017 # License: GPLv2 # # # Set the username env.user = "matt" # Set the password [NOT RECOMMENDED] env.password = "" env.hosts = "" # Generic Exception for capturing Fabric Errors #--------------------------------- class FabricException(Exception): pass env.abort_exception = FabricException #-------------------------------- # Exception Decorater #-------------------------------- def try_to_except(func): def newfunc(): try: func() except FabricException: print("directory already exists!!!") return newfunc # Test Function - Create a Directory on local host # # func1: errors out if a directory already exist at execution # func2: try & except capture error and return a message # the script continues # func3: Same result as func2 with @try_to_except #============================================================ # 1. func1 def test_func1(): local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir") # 2. func2 def test_func2(): try: local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir") except FabricException: print("directory already exists!!!") # 3. func3 @try_to_except def test_func3(): local("mkdir ~matt/git/savelono-projects/exception_wrapper/somedir") #=========================================================== |
No Comments »
RSS feed for comments on this post. TrackBack URL