11import networkx as nx
22import itertools
3+ import re
34from collections import defaultdict , OrderedDict
45from .errors import DataJointError
56
67
8+ def unite_master_parts (lst ):
9+ """
10+ re-order a list of table names so that part tables immediately follow their master tables without breaking
11+ the topological order.
12+ Without this correction, a simple topological sort may insert other descendants between master and parts.
13+ The input list must be topologically sorted.
14+ :example:
15+ unite_master_parts(
16+ ['`s`.`a`', '`s`.`a__q`', '`s`.`b`', '`s`.`c`', '`s`.`c__q`', '`s`.`b__q`', '`s`.`d`', '`s`.`a__r`']) ->
17+ ['`s`.`a`', '`s`.`a__q`', '`s`.`a__r`', '`s`.`b`', '`s`.`b__q`', '`s`.`c`', '`s`.`c__q`', '`s`.`d`']
18+ """
19+ for i in range (2 , len (lst )):
20+ name = lst [i ]
21+ match = re .match (r'(?P<master>`\w+`.`\w+)__\w+`' , name )
22+ if match : # name is a part table
23+ master = match .group ('master' )
24+ for j in range (i - 1 , - 1 , - 1 ):
25+ if lst [j ] == master + '`' or lst [j ].startswith (master + '__' ):
26+ # move from the ith position to the (j+1)th position
27+ lst [j + 1 :i + 1 ] = [name ] + lst [j + 1 :i ]
28+ break
29+ else :
30+ raise DataJointError ("Found a part table {name} without its master table." .format (name = name ))
31+ return lst
32+
33+
734class Dependencies (nx .DiGraph ):
835 """
936 The graph of dependencies (foreign keys) between loaded tables.
@@ -16,15 +43,22 @@ class Dependencies(nx.DiGraph):
1643 def __init__ (self , connection = None ):
1744 self ._conn = connection
1845 self ._node_alias_count = itertools .count ()
46+ self ._loaded = False
1947 super ().__init__ (self )
2048
21- def load (self ):
49+ def clear (self ):
50+ self ._loaded = False
51+ super ().clear ()
52+
53+ def load (self , force = True ):
2254 """
2355 Load dependencies for all loaded schemas.
2456 This method gets called before any operation that requires dependencies: delete, drop, populate, progress.
2557 """
26-
2758 # reload from scratch to prevent duplication of renamed edges
59+ if self ._loaded and not force :
60+ return
61+
2862 self .clear ()
2963
3064 # load primary key info
@@ -77,6 +111,7 @@ def load(self):
77111
78112 if not nx .is_directed_acyclic_graph (self ): # pragma: no cover
79113 raise DataJointError ('DataJoint can only work with acyclic dependencies' )
114+ self ._loaded = True
80115
81116 def parents (self , table_name , primary = None ):
82117 """
@@ -86,6 +121,7 @@ def parents(self, table_name, primary=None):
86121 attribute are considered.
87122 :return: dict of tables referenced by the foreign keys of table
88123 """
124+ self .load (force = False )
89125 return {p [0 ]: p [2 ] for p in self .in_edges (table_name , data = True )
90126 if primary is None or p [2 ]['primary' ] == primary }
91127
@@ -97,6 +133,7 @@ def children(self, table_name, primary=None):
97133 attribute are considered.
98134 :return: dict of tables referencing the table through foreign keys
99135 """
136+ self .load (force = False )
100137 return {p [1 ]: p [2 ] for p in self .out_edges (table_name , data = True )
101138 if primary is None or p [2 ]['primary' ] == primary }
102139
@@ -105,17 +142,19 @@ def descendants(self, full_table_name):
105142 :param full_table_name: In form `schema`.`table_name`
106143 :return: all dependent tables sorted in topological order. Self is included.
107144 """
145+ self .load (force = False )
108146 nodes = self .subgraph (
109147 nx .algorithms .dag .descendants (self , full_table_name ))
110- return [full_table_name ] + list (
111- nx .algorithms .dag .topological_sort (nodes ))
148+ return unite_master_parts ( [full_table_name ] + list (
149+ nx .algorithms .dag .topological_sort (nodes )))
112150
113151 def ancestors (self , full_table_name ):
114152 """
115153 :param full_table_name: In form `schema`.`table_name`
116154 :return: all dependent tables sorted in topological order. Self is included.
117155 """
156+ self .load (force = False )
118157 nodes = self .subgraph (
119158 nx .algorithms .dag .ancestors (self , full_table_name ))
120- return [ full_table_name ] + list (reversed (list (
121- nx .algorithms .dag .topological_sort (nodes ))))
159+ return list (reversed ( unite_master_parts (list (
160+ nx .algorithms .dag .topological_sort (nodes )) + [ full_table_name ]) ))
0 commit comments