1 '''
2 Created on Aug 29, 2009
3
4 @author: tw55413
5
6 http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SchemaDisplay
7 '''
8
9
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
60
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
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
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
164 *edge, **relation_kwargs
165 )
166 graph.add_edge(graph_edge)
167
168
169
170
171
172
173
174
175 return graph
176