Class-based Fabric scripts via a Python metaprogramming hack 2010-09-23 | Tags: ssh | 3 Comments Date:| Tags: python

This is a hack to enable the definition of Fabric tasks as methods in a class instead of just as module level functions. This class-based approach provides the benefits of inheritance and method overriding.

I have a history of using object-oriented techniques in places they weren't meant to be used. This one was not all my idea, so may Andrew get any blame he deserves. Here's the story:

We had several Fabric scripts which violated DRY. Andrew wished for a class-based Fabric script. We discussed ideas. Stackoverflow answered my questions. I hacked. Stackoverflow fixed it for me. I made one more tweak and here it is:

util.py :

import inspect import sys def add_class_methods_as_module_level_functions_for_fabric ( instance , module_name ): ''' Utility to take the methods of the instance of a class, instance, and add them as functions to a module, module_name, so that Fabric can find and call them. Call this at the bottom of a module after the class definition. ''' # get the module as an object module_obj = sys . modules [ module_name ] # Iterate over the methods of the class and dynamically create a function # for each method that calls the method and add it to the current module for method in inspect . getmembers ( instance , predicate = inspect . ismethod ): method_name , method_obj = method if not method_name . startswith ( '_' ): # get the bound method func = getattr ( instance , method_name ) # add the function to the current module setattr ( module_obj , method_name , func )

As the docstring says, this function takes the methods of a class instance and adds them as functions to the module (fabfile.py) so Fabric an find and call them. Here is an example.

base.py :

from fabric import api as fab class Deployment ( object ): name = '' local_file = '' remote_file = '' def base_task1 ( self ): 'base task 1' fab . run ( 'svn export /path/to/{self.name}' . format ( self = self )) def base_task2 ( self ): 'base task 2' fab . put ( self . local_file , self . remote_file )

fabfile.py :

import base import util from fabric import api as fab class _MyWebsiteDeployment ( base . Deployment ): name = 'my_website' local_file = '/local/path/to/my_website/file' remote_file = '/remote/path/to/my_website/file' def my_website_task ( self ): 'my website task' fab . run ( 'echo "I am special"' ) instance = _MyWebsiteDeployment () util . add_class_methods_as_module_level_functions_for_fabric ( instance , __name__ )

Running fab -l gives: