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