@@ -95,13 +95,75 @@ def _rename_attributes(table, props):
9595
9696 def make (self , key ):
9797 """
98- Derived classes must implement method `make` that fetches data from tables
99- above them in the dependency hierarchy, restricting by the given key,
100- computes secondary attributes, and inserts the new tuples into self.
98+ This method must be implemented by derived classes to perform automated computation.
99+ The method must implement the following three steps:
100+
101+ 1. Fetch data from tables above in the dependency hierarchy, restricted by the given key.
102+ 2. Compute secondary attributes based on the fetched data.
103+ 3. Insert the new tuples into the current table.
104+
105+ The method can be implemented either as:
106+ (a) Regular method: All three steps are performed in a single database transaction.
107+ The method must return None.
108+ (b) Generator method:
109+ The make method is split into three functions:
110+ - `make_fetch`: Fetches data from the parent tables.
111+ - `make_compute`: Computes secondary attributes based on the fetched data.
112+ - `make_insert`: Inserts the computed data into the current table.
113+
114+ Then populate logic is executes as follows:
115+
116+ <pseudocode>
117+ fetched_data1 = self.make_fetch(key)
118+ computed_result = self.make_compute(key, *fetched_data1)
119+ begin transaction:
120+ fetched_data2 = self.make_fetch(key)
121+ if fetched_data1 != fetched_data2:
122+ cancel transaction
123+ else:
124+ self.make_insert(key, *computed_result)
125+ commit_transaction
126+ <pseudocode>
127+
128+ Importantly, the output of make_fetch is a tuple that serves as the input into `make_compute`.
129+ The output of `make_compute` is a tuple that serves as the input into `make_insert`.
130+
131+ The functionality must be strictly divided between these three methods:
132+ - All database queries must be completed in `make_fetch`.
133+ - All computation must be completed in `make_compute`.
134+ - All database inserts must be completed in `make_insert`.
135+
136+ DataJoint may programmatically enforce this separation in the future.
137+
138+ :param key: The primary key value used to restrict the data fetching.
139+ :raises NotImplementedError: If the derived class does not implement the required methods.
101140 """
102- raise NotImplementedError (
103- "Subclasses of AutoPopulate must implement the method `make`"
104- )
141+
142+ if not (
143+ hasattr (self , "make_fetch" )
144+ and hasattr (self , "make_insert" )
145+ and hasattr (self , "make_compute" )
146+ ):
147+ # user must implement `make`
148+ raise NotImplementedError (
149+ "Subclasses of AutoPopulate must implement the method `make` or (`make_fetch` + `make_compute` + `make_insert`)"
150+ )
151+
152+ # User has implemented `_fetch`, `_compute`, and `_insert` methods instead
153+
154+ # Step 1: Fetch data from parent tables
155+ fetched_data = self .make_fetch (key ) # fetched_data is a tuple
156+ computed_result = yield fetched_data # passed as input into make_compute
157+
158+ # Step 2: If computed result is not passed in, compute the result
159+ if computed_result is None :
160+ # this is only executed in the first invocation
161+ computed_result = self .make_compute (key , * fetched_data )
162+ yield computed_result # this is passed to the second invocation of make
163+
164+ # Step 3: Insert the computed result into the current table.
165+ self .make_insert (key , * computed_result )
166+ yield
105167
106168 @property
107169 def target (self ):
@@ -352,6 +414,8 @@ def _populate1(
352414 ]
353415 ): # rollback due to referential integrity fail
354416 self .connection .cancel_transaction ()
417+ logger .warning (
418+ f"Referential integrity failed for { key } -> { self .target .full_table_name } " )
355419 return False
356420 gen .send (computed_result ) # insert
357421
0 commit comments