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