09/04/2008

Graphing Pylons SQLAlchemy model paster command

Lovely title i know...

I already posted how to make pretty graphs from your sqlalchemy models, the following is a complete paster command using that code.

import os
import sys

from paste.script.command import Command, BadCommand
from paste.script.filemaker import FileOp
from paste.deploy import loadapp, appconfig
from paste.script.pluginlib import find_egg_info_dir

import pydot

def graph_meta(meta, filename="dbgraph.jpeg"):
    d = pydot.Dot()
    nodes = {}
    
    for table in meta.tables.itervalues():
        n = nodes[table.name] = pydot.Node(table.name)
        d.add_node(n)

    for table in meta.tables.itervalues():
        for c in table.c:
            for fk in c.foreign_keys:
                e = pydot.Edge(nodes[table.name], nodes[fk.column.table.name], 
                    label=fk.column.name)
                d.add_edge(e)

    d.write_jpeg(filename)

 
def can_import(name):
    """Attempt to __import__ the specified package/module, returning True when
    succeeding, otherwise False"""
    try:
        __import__(name)
        return True
    except ImportError:
        return False


class DBGraph(Command):
    summary = 'create a graph of the model'
    parser = Command.standard_parser(simulate=True)
    parser.add_option('--output', '-o',
                      default="dbraph.jpg",
                      dest='output',
                      help="output graph to file (default: dbgraph.jpg)")

    group_name = 'pylons'

    def command(self):
        if len(self.args) == 0:
            # Assume the .ini file is ./development.ini
            config_file = 'development.ini'
            if not os.path.isfile(config_file):
                raise BadCommand('%sError: CONFIG_FILE not found at: .%s%s\n'
                                 'Please specify a CONFIG_FILE' % \
                                 (self.parser.get_usage(), os.path.sep,
                                  config_file))
        else:
            config_file = self.args.pop()

        config_name = 'config:%s' % config_file
        here_dir = os.getcwd()
        locs = dict(__name__="pylons-admin")

        wsgiapp = loadapp(config_name, relative_to=here_dir)

        # Determine the package name from the .egg-info top_level.txt.
        egg_info = find_egg_info_dir(here_dir)
        f = open(os.path.join(egg_info, 'top_level.txt'))
        packages = [l.strip() for l in f.readlines()
                    if l.strip() and not l.strip().startswith('#')]
        f.close()

        # Start the rest of our imports now that the app is loaded
        found_base = False
        for pkg_name in packages:
            # Import all objects from the base module
            base_module = pkg_name + '.lib.base'
            found_base = can_import(base_module)
            if not found_base:
                # Minimal template
                base_module = pkg_name + '.controllers'
                found_base = can_import(base_module)

            if found_base:
                break

        if not found_base:
            raise ImportError("Could not import base module. Are you sure "
                              "this is a Pylons app?")

        base = sys.modules[base_module]
        base_public = [__name for __name in dir(base) if not \
                       __name.startswith('_') or __name == '_']
        for name in base_public:
            locs[name] = getattr(base, name)
        locs.update(dict(wsgiapp=wsgiapp))

        graph_meta(locs['model'].meta, self.options.output)

If you want to use this drop it in a file called "commands.py" inside you pylons app, add the following to your setup.py:

    [paste.paster_command]
    dbgraph = .commands:DBGraph

Then run "python setup.py egg_info" and youll be able to run the command.

If your all very good ill move this to a package and post the egg here... ;)

No comments: