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