11import networkx as nx
22import re
33import numpy as np
4+ import functools
45from scipy .optimize import basinhopping
56import itertools
6- import inspect
77from . import Manual , Imported , Computed , Lookup , Part , DataJointError
88from .base_relation import lookup_class_name
99
10+
1011user_relation_classes = (Manual , Lookup , Computed , Imported , Part )
1112
1213
@@ -23,10 +24,8 @@ class ERD(nx.DiGraph):
2324 Entity relationship diagram.
2425
2526 Usage:
26- >>> erd = Erd(source, context=None )
27+ >>> erd = Erd(source)
2728 source can be a base relation object, a base relation class, a schema, or a module that has a schema.
28- context is the namespace in which to search for classes. If None (default), looks
29- in source.context or source.schema.context
3029
3130 >>> erd.draw()
3231 draws the diagram using pyplot
@@ -39,12 +38,11 @@ class ERD(nx.DiGraph):
3938 Note that erd + 1 - 1 may differ from erd - 1 + 1 and so forth.
4039 Only those tables that are loaded in the connection object are displayed
4140 """
42- def __init__ (self , source , context = None ):
41+ def __init__ (self , source ):
4342
4443 if isinstance (source , ERD ):
4544 # copy constructor
4645 self .nodes_to_show = set (source .nodes_to_show )
47- self ._context = source ._context
4846 super ().__init__ (source )
4947 return
5048
@@ -57,18 +55,6 @@ def __init__(self, source, context=None):
5755 except AttributeError :
5856 raise DataJointError ('Could not find database connection in %s' % repr (source [0 ]))
5957
60- if context is None :
61- # find context in the source
62- try :
63- context = source .context
64- except AttributeError :
65- try :
66- context = source .schema .context
67- except AttributeError :
68- raise DataJointError ('Could not find context in %s' % repr (source [0 ]))
69-
70- self ._context = context
71-
7258 # initialize graph from dependencies
7359 connection .dependencies .load ()
7460 super ().__init__ (connection .dependencies )
@@ -89,6 +75,15 @@ def __init__(self, source, context=None):
8975 if node .startswith ('`%s`' % database ):
9076 self .nodes_to_show .add (node )
9177
78+ @classmethod
79+ def from_sequence (cls , sequence ):
80+ """
81+ The join ERD for all objects in sequence
82+ :param sequence: a sequence (e.g. list, tuple)
83+ :return: ERD(arg1) + ... + ERD(argn)
84+ """
85+ return functools .reduce (lambda x , y : x + y , map (ERD , sequence ))
86+
9287 def __add__ (self , arg ):
9388 """
9489 :param arg: either another ERD or a positive integer.
@@ -131,25 +126,42 @@ def __mul__(self, arg):
131126 self .nodes_to_show .intersection_update (arg .nodes_to_show )
132127 return self
133128
134- def _make_graph (self ):
129+ def _make_graph (self , context ):
135130 """
136131 Make the self.graph - a graph object ready for drawing
137132 """
138133 graph = nx .DiGraph (self ).subgraph (self .nodes_to_show )
139134 nx .set_node_attributes (graph , 'node_type' , {n : _get_tier (n .split ('`' )[- 2 ]) for n in graph })
140135 # relabel nodes to class names
141- mapping = {node : lookup_class_name (self . _context , node ) for node in graph .nodes ()}
136+ mapping = {node : lookup_class_name (node , context ) for node in graph .nodes ()}
142137 new_names = [mapping .values ()]
143138 if len (new_names ) > len (set (new_names )):
144139 raise DataJointError ('Some classes have identical names. The ERD cannot be plotted.' )
145140 nx .relabel_nodes (graph , mapping , copy = False )
146141 return graph
147142
148- def draw (self , pos = None , layout = None , font_scale = 1.2 , ** layout_options ):
143+ def draw (self , pos = None , layout = None , context = None , font_scale = 1.2 , ** layout_options ):
144+ """
145+ Draws the graph of dependencies.
146+ :param pos: dict with positions for every node. If None, then layout is called.
147+ :param layout: the graph layout function. If None, then self._layout is used.
148+ :param context: the context in which to look for the class names. If None, the caller's context is used.
149+ :param font_scale: the scalar used to scale all the fonts.
150+ :param layout_options: kwargs passed into the layout function.
151+ """
149152 if not self .nodes_to_show :
150153 print ('There is nothing to plot' )
151154 return
152- graph = self ._make_graph ()
155+ if context is None :
156+ # get the caller's locals()
157+ import inspect
158+ frame = inspect .currentframe ()
159+ try :
160+ context = frame .f_back .f_locals
161+ finally :
162+ del frame
163+
164+ graph = self ._make_graph (context )
153165 if pos is None :
154166 pos = (layout if layout else self ._layout )(graph , ** layout_options )
155167 import matplotlib .pyplot as plt
@@ -158,7 +170,7 @@ def draw(self, pos=None, layout=None, font_scale=1.2, **layout_options):
158170 edge_styles = ['solid' if e [2 ]['primary' ] else 'dashed' for e in edge_list ]
159171 nx .draw_networkx_edges (graph , pos = pos , edgelist = edge_list , style = edge_styles , alpha = 0.2 )
160172
161- label_props = { # http://matplotlib.org/examples/color/named_colors.html
173+ label_props = { # http://matplotlib.org/examples/color/named_colors.html
162174 None : dict (bbox = dict (boxstyle = 'round,pad=0.1' , facecolor = 'yellow' , alpha = 0.3 ), size = round (font_scale * 8 )),
163175 Manual : dict (bbox = dict (boxstyle = 'round,pad=0.1' , edgecolor = 'white' , facecolor = 'darkgreen' , alpha = 0.3 ), size = round (font_scale * 10 )),
164176 Lookup : dict (bbox = dict (boxstyle = 'round,pad=0.1' , edgecolor = 'white' , facecolor = 'gray' , alpha = 0.2 ), size = round (font_scale * 8 )),
0 commit comments