# HG changeset patch # User Sandro Knauß # Date 1744672856 -7200 # Node ID a88add2b3eea21c26d76e099f79cc281a1f1c69c # Parent 42fd5075a5d1c24690b7a67443e33170c0dcc953 use sqlalchemy_schemadisplay module diff -r 42fd5075a5d1 -r a88add2b3eea createerm.py --- a/createerm.py Tue Apr 15 01:19:47 2025 +0200 +++ b/createerm.py Tue Apr 15 01:20:56 2025 +0200 @@ -36,8 +36,10 @@ #schema plot def createSchemaPlot(fname): - from sqlalchemy_schemadisplay3 import create_schema_graph - graph = create_schema_graph(metadata=schema.Base.metadata, + from sqlalchemy_schemadisplay import create_schema_graph + graph = create_schema_graph(engine=None, + metadata=schema.Base.metadata, + tables=tables, show_datatypes=True, # The image too large if datatypes shown show_indexes=True, # ditto for indexes rankdir='LR', # From left to right (instead of top to bottom) @@ -47,25 +49,3 @@ graph.set_size('6.5,10') #graph.set_ratio("fill") graph.write_svg(fname) - -#umlplot -def createUMLPlot(fname): - from sqlalchemy_schemadisplay3 import create_uml_graph - from sqlalchemy.orm import class_mapper - mappers = [] - for attr in dir(schema.model): - if attr[0] == '_': continue - try: - cls = getattr(schema.model, attr) - mappers.append(class_mapper(cls)) - except: - pass - #pass them to the function and set some formatting options - graph = create_uml_graph(mappers, - show_operations=False, # not necessary in this case - show_multiplicity_one=True, # some people like to see the ones - show_attributes=True, - ) - graph.set_size('6,5') - graph.set_ratio("fill") - graph.write_png('test.png') diff -r 42fd5075a5d1 -r a88add2b3eea sqlalchemy_schemadisplay3.py --- a/sqlalchemy_schemadisplay3.py Tue Apr 15 01:19:47 2025 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,194 +0,0 @@ -# Copyright (c) 2012 netzguerilla.net -# -# This file is part of Iro. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of -# this software and associated documentation files (the "Software"), to deal in -# the Software without restriction, including without limitation the rights to use, -# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the -# #Software, and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -# updated SQLA schema display to work with pydot 1.0.2 -# download from: http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SchemaDisplay - - -from sqlalchemy.orm.properties import PropertyLoader -import pydot -import types - -__all__ = ['create_uml_graph', 'create_schema_graph', 'show_uml_graph', 'show_schema_graph'] - -def _mk_label(mapper, show_operations, show_attributes, show_datatypes, bordersize): - html = '<' % (bordersize, mapper.class_.__name__) - def format_col(col): - colstr = '+%s' % (col.name) - if show_datatypes: - colstr += ' : %s' % (col.type.__class__.__name__) - return colstr - - if show_attributes: - html += '' % '
'.join(format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key)) - else: - [format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key)] - if show_operations: - html += '' % '
'.join( - '%s(%s)' % (name,", ".join(default is _mk_label and ("%s") % arg or ("%s=%s" % (arg,repr(default))) for default,arg in - 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:]) - )) - for name,func in mapper.class_.__dict__.items() if isinstance(func, types.FunctionType) and func.__module__ == mapper.class_.__module__ - ) - html+= '
%s
%s
%s
>' - return html - - -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"): - graph = pydot.Dot(prog='neato',mode="major",overlap="0", sep="0.01",dim="3", pack="True", ratio=".75") - relations = set() - for mapper in mappers: - graph.add_node(pydot.Node(mapper.class_.__name__, - shape="plaintext", label=_mk_label(mapper, show_operations, show_attributes, show_datatypes, linewidth), - fontname=font, fontsize="8.0", - )) - if mapper.inherits: - graph.add_edge(pydot.Edge(mapper.inherits.class_.__name__,mapper.class_.__name__, - arrowhead='none',arrowtail='empty', style="setlinewidth(%s)" % linewidth, arrowsize=str(linewidth))) - for loader in mapper.iterate_properties: - if isinstance(loader, PropertyLoader) and loader.mapper in mappers: - if hasattr(loader, 'reverse_property'): - relations.add(frozenset([loader, loader.reverse_property])) - else: - relations.add(frozenset([loader])) - - for relation in relations: - #if len(loaders) > 2: - # raise Exception("Warning: too many loaders for join %s" % join) - args = {} - def multiplicity_indicator(prop): - if prop.uselist: - return ' *' - if any(col.nullable for col in prop.local_side): - return ' 0..1' - if show_multiplicity_one: - return ' 1' - return '' - - if len(relation) == 2: - src, dest = relation - from_name = src.parent.class_.__name__ - to_name = dest.parent.class_.__name__ - - def calc_label(src,dest): - return '+' + src.key + multiplicity_indicator(src) - args['headlabel'] = calc_label(src,dest) - - args['taillabel'] = calc_label(dest,src) - args['arrowtail'] = 'none' - args['arrowhead'] = 'none' - args['constraint'] = False - else: - prop, = relation - from_name = prop.parent.class_.__name__ - to_name = prop.mapper.class_.__name__ - args['headlabel'] = '+%s%s' % (prop.key, multiplicity_indicator(prop)) - args['arrowtail'] = 'none' - args['arrowhead'] = 'vee' - - graph.add_edge(pydot.Edge(from_name,to_name, - fontname=font, fontsize="7.0", style="setlinewidth(%s)"%linewidth, arrowsize=str(linewidth), - **args) - ) - - return graph - -#from sqlalchemy import Table, text - -def _render_table_html(table, metadata, show_indexes, show_datatypes): - def format_col_type(col): - try: - return col.type.get_col_spec() - except NotImplementedError: - return str(col.type) - except AttributeError: - return str(col.type) - def format_col_str(col): - if show_datatypes: - return "- %s : %s" % (col.name, format_col_type(col)) - else: - return "- %s" % col.name - html = '<' % table.name - - html += ''.join('' % (col.name, format_col_str(col)) for col in table.columns) - html += '
%s
%s
>' - return html - -def create_schema_graph(tables=None, metadata=None, show_indexes=True, show_datatypes=True, font="Bitstream-Vera Sans", - concentrate=True, relation_options={}, rankdir='TB'): - relation_kwargs = { - 'fontsize':"7.0" - } - relation_kwargs.update(relation_options) - - if not metadata and len(tables): - metadata = tables[0].metadata - elif not tables and metadata: - if not len(metadata.tables): - metadata.reflect() - tables = metadata.tables.values() - else: - raise Exception("You need to specify at least tables or metadata") - - graph = pydot.Dot(prog="dot",mode="ipsep",overlap="ipsep",sep="0.01",concentrate=str(concentrate), rankdir=rankdir) - for table in tables: - graph.add_node(pydot.Node(str(table.name), - shape="plaintext", - label=_render_table_html(table, metadata, show_indexes, show_datatypes), - fontname=font, fontsize="7.0" - )) - - for table in tables: - for fk in table.foreign_keys: - edge = [table.name, fk.column.table.name] - is_inheritance = fk.parent.primary_key and fk.column.primary_key - if is_inheritance: - edge = edge[::-1] - graph_edge = pydot.Edge( - headlabel="+ %s"%fk.column.name, taillabel='+ %s'%fk.parent.name, - arrowhead=is_inheritance and 'none' or 'odot' , - arrowtail=(fk.parent.primary_key or fk.parent.unique) and 'empty' or 'crow' , - fontname=font, - #samehead=fk.column.name, sametail=fk.parent.name, - *edge, **relation_kwargs - ) - graph.add_edge(graph_edge) - -# not sure what this part is for, doesn't work with pydot 1.0.2 -# graph_edge.parent_graph = graph.parent_graph -# if table.name not in [e.get_source() for e in graph.get_edge_list()]: -# graph.edge_src_list.append(table.name) -# if fk.column.table.name not in graph.edge_dst_list: -# graph.edge_dst_list.append(fk.column.table.name) -# graph.sorted_graph_elements.append(graph_edge) - return graph - -def show_uml_graph(*args, **kwargs): - from cStringIO import StringIO - from PIL import Image - iostream = StringIO(create_uml_graph(*args, **kwargs).create_png()) - Image.open(iostream).show(command=kwargs.get('command','gwenview')) - -def show_schema_graph(*args, **kwargs): - from cStringIO import StringIO - from PIL import Image - iostream = StringIO(create_schema_graph(*args, **kwargs).create_png()) - Image.open(iostream).show(command=kwargs.get('command','gwenview')) diff -r 42fd5075a5d1 -r a88add2b3eea web/images/db-schema.svg --- a/web/images/db-schema.svg Tue Apr 15 01:19:47 2025 +0200 +++ b/web/images/db-schema.svg Tue Apr 15 01:20:56 2025 +0200 @@ -1,99 +1,128 @@ - - - + + G - - -message - -message - -- id : INTEGER -- recipient : VARCHAR(100) -- isBilled : BOOLEAN -- date : DATETIME -- price : NUMERIC(8, 4) -- count : INTEGER -- exID : VARCHAR(100) -- job : VARCHAR(40) -- offer : VARCHAR(100) + + + +apiuser +apiuser + +- name : VARCHAR(100) +- ng_kunde : INTEGER +- apikey : VARCHAR(50) + - -job - -job - -- id : INTEGER -- info : VARCHAR(100) -- status : VARCHAR(7) -- user : VARCHAR(100) + + +userright +userright + +- user : VARCHAR(100) +- offer : VARCHAR(100) +- default : INTEGER + - -message->job - - -+ id -+ job + + +apiuser->userright + + ++ name ++ user -offer - -offer - -- name : VARCHAR(100) -- provider : VARCHAR(100) -- route : VARCHAR(100) -- typ : VARCHAR(100) + +offer +offer + +- name : VARCHAR(100) +- provider : VARCHAR(100) +- route : VARCHAR(100) +- typ : VARCHAR(100) + + + + +offer->userright + + ++ name ++ offer + + + +status +status + +- id : INTEGER +- date : DATETIME +- recipient : VARCHAR(100) +- exID : VARCHAR(100) +- status : INTEGER +- sender : VARCHAR(100) +- data : VARCHAR(2550) + + + + +job +job + +- id : INTEGER +- info : VARCHAR(100) +- status : VARCHAR(7) +- user : VARCHAR(100) + + + + +job->apiuser + + + ++ name ++ user + + + +message +message + +- id : INTEGER +- recipient : VARCHAR(100) +- isBilled : BOOLEAN +- date : DATETIME +- price : NUMERIC(8, 4) +- count : INTEGER +- exID : VARCHAR(100) +- job : VARCHAR(40) +- offer : VARCHAR(100) + -message->offer - - -+ name -+ offer - - -userright - -userright - -- user : VARCHAR(100) -- offer : VARCHAR(100) -- default : INTEGER + +message->offer + + + ++ name ++ offer - -apiuser - -apiuser - -- name : VARCHAR(100) -- ng_kunde : INTEGER -- apikey : VARCHAR(50) - - -job->apiuser - - -+ name -+ user - - -apiuser->userright - -+ name -+ user - - -offer->userright - -+ name -+ offer + + +message->job + + + ++ id ++ job