Package Camelot :: Package camelot :: Package core :: Module schema_display |
[frames] | no frames]

# Source Code for Module Camelot.camelot.core.schema_display

      1  '''
      2  Created on Aug 29, 2009
      3
      4  @author: tw55413
      5
      6  http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SchemaDisplay
      7  '''
      8
      9  # updated SQLA schema display to work with pydot 1.0.2
     10
     11  from sqlalchemy.orm.properties import PropertyLoader
     12  import pydot
     13  import types
     14
     15  __all__ = ['create_uml_graph', 'create_schema_graph',]
     16
     17 -def _mk_label(mapper, show_operations, show_attributes, show_datatypes, bordersize):
     18      html = '<<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="%d" BALIGN="LEFT"><TR><TD><FONT POINT-SIZE="10">%s</FONT></TD></TR>' % (bordersize, mapper.class_.__name__)
     19      def format_col(col):
     20          colstr = '+%s' % (col.name)
     21          if show_datatypes:
     22              colstr += ' : %s' % (col.type.__class__.__name__)
     23          return colstr
     24
     25      if show_attributes:
     26          html += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % '<BR ALIGN="LEFT"/>'.join(format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key))
     27      else:
     28          [format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key)]
     29      if show_operations:
     30          html += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % '<BR ALIGN="LEFT"/>'.join(
     31              '%s(%s)' % (name,", ".join(default is _mk_label and ("%s") % arg or ("%s=%s" % (arg,repr(default))) for default,arg in
     32                  zip((func.func_defaults and len(func.func_code.co_varnames)-1-(len(func.func_defaults) or 0) or func.func_code.co_argcount-1)*[_mk_label]+list(func.func_defaults or []), func.func_code.co_varnames[1:])
     33              ))
     34              for name,func in mapper.class_.__dict__.items() if isinstance(func, types.FunctionType) and func.__module__ == mapper.class_.__module__
     35          )
     36      html+= '</TABLE>>'
     37      return html
     38
     39
     40 -def create_uml_graph(mappers, show_operations=True, show_attributes=True, show_multiplicity_one=False, show_datatypes=True, linewidth=1.0, font="Bitstream-Vera Sans"):
     41      graph = pydot.Dot(prog='neato',mode="major",overlap="0", sep="0.01",dim="3", pack="True", ratio=".75")
     42      relations = set()
     43      for mapper in mappers:
     44          graph.add_node(pydot.Node(mapper.class_.__name__,
     45              shape="plaintext", label=_mk_label(mapper, show_operations, show_attributes, show_datatypes, linewidth),
     46              fontname=font, fontsize="8.0",
     47          ))
     48          if mapper.inherits:
     49              graph.add_edge(pydot.Edge(mapper.inherits.class_.__name__,mapper.class_.__name__,
     50                  arrowhead='none',arrowtail='empty', style="setlinewidth(%s)" % linewidth, arrowsize=str(linewidth)))
     51          for loader in mapper.iterate_properties:
     52              if isinstance(loader, PropertyLoader) and loader.mapper in mappers:
     53                  if hasattr(loader, 'reverse_property'):
     54                      relations.add(frozenset([loader, loader.reverse_property]))
     55                  else:
     56                      relations.add(frozenset([loader]))
     57
     58      for relation in relations:
     59          #if len(loaders) > 2:
     60          #    raise Exception("Warning: too many loaders for join %s" % join)
     61          args = {}
     62          def multiplicity_indicator(prop):
     63              if prop.uselist:
     64                  return ' *'
     65              if any(col.nullable for col in prop.local_side):
     66                  return ' 0..1'
     67              if show_multiplicity_one:
     68                  return ' 1'
     69              return ''
     70
     71          if len(relation) == 2:
     72              src, dest = relation
     73              from_name = src.parent.class_.__name__
     74              to_name = dest.parent.class_.__name__
     75
     76              def calc_label(src,dest):
     77                  return '+' + src.key + multiplicity_indicator(src)
     78              args['headlabel'] = calc_label(src,dest)
     79
     80              args['taillabel'] = calc_label(dest,src)
     81              args['arrowtail'] = 'none'
     82              args['arrowhead'] = 'none'
     83              args['constraint'] = False
     84          else:
     85              prop, = relation
     86              from_name = prop.parent.class_.__name__
     87              to_name = prop.mapper.class_.__name__
     88              args['headlabel'] = '+%s%s' % (prop.key, multiplicity_indicator(prop))
     89              args['arrowtail'] = 'none'
     90              args['arrowhead'] = 'vee'
     91
     92          graph.add_edge(pydot.Edge(from_name,to_name,
     93              fontname=font, fontsize="7.0", style="setlinewidth(%s)"%linewidth, arrowsize=str(linewidth),
     94              **args)
     95          )
     96
     97      return graph
     98
     99  from sqlalchemy.databases.postgres import PGDialect
    100  from sqlalchemy import text
    101
    102 -def _render_table_html(table, metadata, show_indexes, show_datatypes):
    103      def format_col_type(col):
    104          try:
    105              return col.type.get_col_spec()
    106          except NotImplementedError:
    107              return str(col.type)
    108      def format_col_str(col):
    109          if show_datatypes:
    110              return "- %s : %s" % (col.name, format_col_type(col))
    111          else:
    112              return "- %s" % col.name
    113      html = '<<TABLE BORDER="1" CELLBORDER="0" CELLSPACING="0"><TR><TD ALIGN="CENTER">%s</TD></TR><TR><TD BORDER="1" CELLPADDING="0"></TD></TR>' % table.name
    114
    115      html += ''.join('<TR><TD ALIGN="LEFT" PORT="%s">%s</TD></TR>' % (col.name, format_col_str(col)) for col in table.columns)
    116      if metadata.bind and isinstance(metadata.bind.dialect, PGDialect):
    117          # postgres engine doesn't reflect indexes
    118          indexes = dict((name,defin) for name,defin in metadata.bind.execute(text("SELECT indexname, indexdef FROM pg_indexes WHERE tablename = '%s'" % table.name)))
    119          if indexes and show_indexes:
    120              html += '<TR><TD BORDER="1" CELLPADDING="0"></TD></TR>'
    121              for _index, defin in indexes.items():
    122                  ilabel = 'UNIQUE' in defin and 'UNIQUE ' or 'INDEX '
    123                  ilabel += defin[defin.index('('):]
    124                  html += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % ilabel
    125      html += '</TABLE>>'
    126      return html
    127
    128 -def create_schema_graph(tables=None, metadata=None, show_indexes=True, show_datatypes=True, font="Bitstream-Vera Sans",
    129      concentrate=True, relation_options={}, rankdir='TB'):
    130      relation_kwargs = {
    131          'fontsize':"7.0"
    132      }
    133      relation_kwargs.update(relation_options)
    134
    135      if not metadata and len(tables):
    136          metadata = tables[0].metadata
    137      elif not tables and metadata:
    138          if not len(metadata.tables):
    139              metadata.reflect()
    140          tables = metadata.tables.values()
    141      else:
    142          raise Exception("You need to specify at least tables or metadata")
    143
    144      graph = pydot.Dot(prog="dot",mode="ipsep",overlap="ipsep",sep="0.01",concentrate=str(concentrate), rankdir=rankdir)
    145      for table in tables:
    146          graph.add_node(pydot.Node(str(table.name),
    147              shape="plaintext",
    148              label=_render_table_html(table, metadata, show_indexes, show_datatypes),
    149              fontname=font, fontsize="7.0"
    150          ))
    151
    152      for table in tables:
    153          for fk in table.foreign_keys:
    154              edge = [table.name, fk.column.table.name]
    155              is_inheritance = fk.parent.primary_key and fk.column.primary_key
    156              if is_inheritance:
    157                  edge = edge[::-1]
    158              graph_edge = pydot.Edge(
    159                  headlabel="+ %s"%fk.column.name, taillabel='+ %s'%fk.parent.name,
    160                  arrowhead=is_inheritance and 'none' or 'odot' ,
    161                  arrowtail=(fk.parent.primary_key or fk.parent.unique) and 'empty' or 'crow' ,
    162                  fontname=font,
    163                  #samehead=fk.column.name, sametail=fk.parent.name,
    164                  *edge, **relation_kwargs
    165              )
    166              graph.add_edge(graph_edge)
    167
    168  # not sure what this part is for, doesn't work with pydot 1.0.2
    169  #            graph_edge.parent_graph = graph.parent_graph
    170  #            if table.name not in [e.get_source() for e in graph.get_edge_list()]:
    171  #                graph.edge_src_list.append(table.name)
    172  #            if fk.column.table.name not in graph.edge_dst_list:
    173  #                graph.edge_dst_list.append(fk.column.table.name)
    174  #            graph.sorted_graph_elements.append(graph_edge)
    175      return graph
    176