Archive for the ‘Python’ Category

Python Style Plugins Made Easy

Wednesday, January 2nd, 2008


Sometimes you need to write code that loads python at runtime. Plugin architectures are a good example of this. Plugins allow extensibility but more importantly (for me at least) they enforce a strict API. Anyway, I've written this code a few times so I thought I'd modularize it.

The specific bit of code I am going to post is the python code to look for and load a series of python plugins. Plugins (in this case) are just classes that are subclasses of the Plugin base class. This plugin base class dictates the API that all plugins must implement. Here is an example plugin abstract base class

class Plugin(object):
    def setup(self):
        """called before the plugin is asked to do anything"""
        raise NotImplementedError
 
    def teardown(self):
        """called to allow the plugin to free anything"""
        raise NotImplementedError
 
    def domagic(self):
        """do whatever it is the plugin does"""
        raise NotImplementedError
 

Here the is code to look for and return a list of classes that can be instantiated:

def find_subclasses(path, cls):
    """
    Find all subclass of cls in py files located below path
    (does look in sub directories)
 
    @param path: the path to the top level folder to walk
    @type path: str
    @param cls: the base class that all subclasses should inherit from
    @type cls: class
    @rtype: list
    @return: a list if classes that are subclasses of cls
    """
 
    subclasses=[]
 
    def look_for_subclass(modulename):
        log.debug("searching %s" % (modulename))
        module=__import__(modulename)
 
        #walk the dictionaries to get to the last one
        d=module.__dict__
        for m in modulename.split('.')[1:]:
            d=d[m].__dict__
 
        #look through this dictionary for things
        #that are subclass of Job
        #but are not Job itself
        for key, entry in d.items():
            if key == cls.__name__:
                continue
 
            try:
                if issubclass(entry, cls):
                    log.debug("Found subclass: "+key)
                    subclasses.append(entry)
            except TypeError:
                #this happens when a non-type is passed in to issubclass. We
                #don't care as it can't be a subclass of Job if it isn't a
                #type
                continue
 
    for root, dirs, files in os.walk(path):
        for name in files:
            if name.endswith(".py") and not name.startswith("__"):
                path = os.path.join(root, name)
                modulename = path.rsplit('.', 1)[0].replace('/', '.')
                look_for_subclass(modulename)
 
    return subclasses
 

and here is how you would call it:

classes = find_subclasses("./pluginsfolder/", Plugin)
 
#lets create an instance of the first class
inst = classes[0]()
 

So there you go, create folder of python files with classes in them that subclass the Plugin class and you are away.

Total Guitar Magazine: Decryption Error

Tuesday, December 4th, 2007


Like most metalheads I own a guitar I can't play. I also subscribe to Total Guitar Magazine. This month I noticed that I couldn't decrypt the special 'VIP Area' of the CD.

Bugger-1

Basically If I was using a PPC Mac I would be able to decrypt the data. It seems they have included the PPC only shared library for blowfish. Hmm. Well as it happens I have a copy of the x86 library that I can use to decrypt the data.

If you are suffering from this problem:

  1. Download the library here.
  2. Look inside any OSX app by right clicking (or CTRL clicking) on the application icon and selecting 'Show Package Contents'
    Showcontent
  3. Now navigate down to
    Contents/Resources/lib/python2.5/lib-dynload/Crypto/Cipher/
  4. Copy the Blowfish.so into the Cipher Folder, there should be one there already so you'll be asked if you want to replace the existing one

You should be able to run the decryptor.app now and get access to your MP3s.
Whoever makes this application should be able to make the decryption library Universal so lets hope they do that in the future.

PostgreSQL, SQLAlchemy, Dropping all tables and sequences

Friday, November 23rd, 2007


I've been working in deployment scripts for my current project and sometimes I need to drop all the tables and sequences from the database so I can create everything from scratch. I had been doing a

 
DROP SCHEMA public CASCADE;
CREATE SCHEMA public AUTHORIZATION bob;
GRANT ALL ON SCHEMA public TO bob;
 

This was great but a little destructive and over the top. It removes stored procedures and triggers as well which isn't what I want. So I looked at the SQLAlchemy docs and there is a metadata command drop_all()

 
def drop_tables():
    metadata.drop_tables()
 

Unfortunately this doesn't CASCADE so this isn't going to work in cases where tables have foreign keys (which is most of the time). It also leaves sequences in place.

So I looked at dropping each table in turn with a cascade. So how do you get a list of tables and sequences in the database? I could hard code the names of the tables and sequences but that seemed a little poor.

It turns out that PostgreSQL has a set of views that expose the inner workings of your database and a few queries can give you all the information you need.

 
def get_table_list_from_db():
    """
    return a list of table names from the current
    databases public schema
    """
    sql="select table_name from information_schema.tables"\
        "where table_schema='public'"
    execute = metadata.execute
    return [name for (name, ) in execute(text(sql))]
 
def get_seq_list_from_db():
    """return a list of the sequence names from the current
       databases public schema
    """
    sql="select sequence_name from information_schema.sequences"\
        "where sequence_schema='public'"
    execute = metadata.execute
    return [name for (name, ) in execute(text(sql))]
 
def drop_all_tables_and_sequences():
    execute = metadata.execute
    for table in get_table_list_from_db():
        try:
            execute(text("DROP TABLE %s CASCADE" % table))
        except SQLError, e:
            print e
 
    for seq in get_seq_list_from_db():
        try:
            execute(text("DROP SEQUENCE %s CASCADE" % table))
        except SQLError, e:
            print e
 

The information_schema is full of interesting information. It's actually a 'view' of lower level database tables. You can find all sorts of performance and configuration information in there. Quite handy :) Here are some docs.

Good luck.