1111from .declare import declare , alter
1212from .expression import QueryExpression
1313from . import blob
14- from .utils import user_choice
14+ from .utils import user_choice , OrderedDict
1515from .heading import Heading
1616from .errors import DuplicateError , AccessError , DataJointError , UnknownAttributeError
1717from .version import __version__ as version
@@ -40,8 +40,7 @@ class Table(QueryExpression):
4040 @property
4141 def heading (self ):
4242 """
43- Returns the table heading. If the table is not declared, attempts to declare it and return heading.
44- :return: table heading
43+ :return: table heading. If the table is not declared, attempts to declare it first.
4544 """
4645 if self ._heading is None :
4746 self ._heading = Heading () # instance-level heading
@@ -53,7 +52,8 @@ def heading(self):
5352 def declare (self , context = None ):
5453 """
5554 Declare the table in the schema based on self.definition.
56- :param context: the context for foreign key resolution. If None, foreign keys are not allowed.
55+ :param context: the context for foreign key resolution. If None, foreign keys are
56+ not allowed.
5757 """
5858 if self .connection .in_transaction :
5959 raise DataJointError ('Cannot declare new tables inside a transaction, '
@@ -116,38 +116,59 @@ def get_select_fields(self, select_fields=None):
116116 """
117117 return '*' if select_fields is None else self .heading .project (select_fields ).as_sql
118118
119- def parents (self , primary = None , as_objects = False ):
119+ def parents (self , primary = None , as_objects = False , foreign_key_info = False ):
120120 """
121121 :param primary: if None, then all parents are returned. If True, then only foreign keys composed of
122- primary key attributes are considered. If False, the only foreign keys including at least one non-primary
123- attribute are considered.
124- :param as_objects: if False (default), the output is a dict describing the foreign keys. If True, return table objects.
125- :return: dict of tables referenced with self's foreign keys or list of table objects if as_objects=True
126- """
127- parents = self .connection .dependencies .parents (self .full_table_name , primary )
122+ primary key attributes are considered. If False, return foreign keys including at least one
123+ secondary attribute.
124+ :param as_objects: if False, return table names. If True, return table objects.
125+ :param foreign_key_info: if True, each element in result also includes foreign key info.
126+ :return: list of parents as table names or table objects
127+ with (optional) foreign key information.
128+ """
129+ get_edge = self .connection .dependencies .parents
130+ nodes = [next (iter (get_edge (name ).items ())) if name .isdigit () else (name , props )
131+ for name , props in get_edge (self .full_table_name , primary ).items ()]
128132 if as_objects :
129- parents = [FreeTable (self .connection , c ) for c in parents ]
130- return parents
133+ nodes = [(FreeTable (self .connection , name ), props ) for name , props in nodes ]
134+ if not foreign_key_info :
135+ nodes = [name for name , props in nodes ]
136+ return nodes
131137
132- def children (self , primary = None , as_objects = False ):
138+ def children (self , primary = None , as_objects = False , foreign_key_info = False ):
133139 """
134140 :param primary: if None, then all children are returned. If True, then only foreign keys composed of
135- primary key attributes are considered. If False, the only foreign keys including at least one non-primary
136- attribute are considered.
137- :param as_objects: if False (default), the output is a dict describing the foreign keys. If True, return table objects.
138- :return: dict of tables with foreign keys referencing self or list of table objects if as_objects=True
139- """
140- nodes = dict ((next (iter (self .connection .dependencies .children (k ).items ())) if k .isdigit () else (k , v ))
141- for k , v in self .connection .dependencies .children (self .full_table_name , primary ).items ())
141+ primary key attributes are considered. If False, return foreign keys including at least one
142+ secondary attribute.
143+ :param as_objects: if False, return table names. If True, return table objects.
144+ :param foreign_key_info: if True, each element in result also includes foreign key info.
145+ :return: list of children as table names or table objects
146+ with (optional) foreign key information.
147+ """
148+ get_edge = self .connection .dependencies .children
149+ nodes = [next (iter (get_edge (name ).items ())) if name .isdigit () else (name , props )
150+ for name , props in get_edge (self .full_table_name , primary ).items ()]
142151 if as_objects :
143- nodes = [FreeTable (self .connection , c ) for c in nodes ]
152+ nodes = [(FreeTable (self .connection , name ), props ) for name , props in nodes ]
153+ if not foreign_key_info :
154+ nodes = [name for name , props in nodes ]
144155 return nodes
145156
146157 def descendants (self , as_objects = False ):
147- nodes = [node for node in self .connection .dependencies .descendants (self .full_table_name ) if not node .isdigit ()]
148- if as_objects :
149- nodes = [FreeTable (self .connection , c ) for c in nodes ]
150- return nodes
158+ """
159+ :param as_objects: False - a list of table names; True - a list of table objects.
160+ :return: list of tables descendants in topological order.
161+ """
162+ return [FreeTable (self .connection , node ) if as_objects else node
163+ for node in self .connection .dependencies .descendants (self .full_table_name ) if not node .isdigit ()]
164+
165+ def ancestors (self , as_objects = False ):
166+ """
167+ :param as_objects: False - a list of table names; True - a list of table objects.
168+ :return: list of tables ancestors in topological order.
169+ """
170+ return [FreeTable (self .connection , node ) if as_objects else node
171+ for node in self .connection .dependencies .ancestors (self .full_table_name ) if not node .isdigit ()]
151172
152173 def parts (self , as_objects = False ):
153174 """
@@ -156,13 +177,7 @@ def parts(self, as_objects=False):
156177 """
157178 nodes = [node for node in self .connection .dependencies .nodes
158179 if not node .isdigit () and node .startswith (self .full_table_name [:- 1 ] + '__' )]
159- if as_objects :
160- nodes = [FreeTable (self .connection , c ) for c in nodes ]
161- return nodes
162-
163- def ancestors (self , as_objects = False ):
164- return [FreeTable (self .connection , node ) if as_objects else node
165- for node in self .connection .dependencies .ancestors (self .full_table_name ) if not node .isdigit ()]
180+ return [FreeTable (self .connection , c ) for c in nodes ] if as_objects else nodes
166181
167182 @property
168183 def is_declared (self ):
@@ -525,7 +540,7 @@ def describe(self, context=None, printout=True):
525540 del frame
526541 if self .full_table_name not in self .connection .dependencies :
527542 self .connection .dependencies .load ()
528- parents = self .parents ()
543+ parents = self .parents (foreign_key_info = True )
529544 in_key = True
530545 definition = ('# ' + self .heading .table_info ['comment' ] + '\n '
531546 if self .heading .table_info ['comment' ] else '' )
@@ -538,11 +553,10 @@ def describe(self, context=None, printout=True):
538553 in_key = False
539554 attributes_thus_far .add (attr .name )
540555 do_include = True
541- for parent_name , fk_props in list ( parents . items ()): # need list() to force a copy
556+ for parent_name , fk_props in parents :
542557 if attr .name in fk_props ['attr_map' ]:
543558 do_include = False
544559 if attributes_thus_far .issuperset (fk_props ['attr_map' ]):
545- parents .pop (parent_name )
546560 # foreign key properties
547561 try :
548562 index_props = indexes .pop (tuple (fk_props ['attr_map' ]))
@@ -552,19 +566,19 @@ def describe(self, context=None, printout=True):
552566 index_props = [k for k , v in index_props .items () if v ]
553567 index_props = ' [{}]' .format (', ' .join (index_props )) if index_props else ''
554568
555- if not parent_name . isdigit () :
569+ if not fk_props [ 'aliased' ] :
556570 # simple foreign key
557571 definition += '->{props} {class_name}\n ' .format (
558572 props = index_props ,
559573 class_name = lookup_class_name (parent_name , context ) or parent_name )
560574 else :
561575 # projected foreign key
562- parent_name = list (self .connection .dependencies .in_edges (parent_name ))[0 ][0 ]
563- lst = [(attr , ref ) for attr , ref in fk_props ['attr_map' ].items () if ref != attr ]
564576 definition += '->{props} {class_name}.proj({proj_list})\n ' .format (
565577 props = index_props ,
566578 class_name = lookup_class_name (parent_name , context ) or parent_name ,
567- proj_list = ',' .join ('{}="{}"' .format (a , b ) for a , b in lst ))
579+ proj_list = ',' .join (
580+ '{}="{}"' .format (attr , ref )
581+ for attr , ref in fk_props ['attr_map' ].items () if ref != attr ))
568582 attributes_declared .update (fk_props ['attr_map' ])
569583 if do_include :
570584 attributes_declared .add (attr .name )
0 commit comments