Class-based Fabric scripts via a Python metaprogramming hack
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:
$ fab -l
Available commands:
base_task1 base task 1
base_task2 base task 2
my_website_task my website task
Related posts
- Notes on Fabric 2 and Python 3 — posted 2021-02-07
- How to expose a Flask local development server to the public using SSH remote port forwarding — posted 2013-02-12
- How to run a Django local development server on a remote machine and access it in your browser on your local machine using SSH port forwarding — posted 2012-10-23
- Notes on debugging ssh connection problems — posted 2011-08-31
- Fabric post-run processing Python decorator — posted 2010-11-06
Comments
Nice stuff. Exactly what I needed. Thanks for doing the hard work and posting!
Thank you very much. Was looking for days on how to do this. My file has grown to be very huge already.
This is awesome, exactly what I'm looking for!! Thanks!