@@ -95,13 +95,75 @@ def _rename_attributes(table, props):
95
95
96
96
def make (self , key ):
97
97
"""
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.
101
140
"""
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
105
167
106
168
@property
107
169
def target (self ):
@@ -352,6 +414,8 @@ def _populate1(
352
414
]
353
415
): # rollback due to referential integrity fail
354
416
self .connection .cancel_transaction ()
417
+ logger .warning (
418
+ f"Referential integrity failed for { key } -> { self .target .full_table_name } " )
355
419
return False
356
420
gen .send (computed_result ) # insert
357
421
0 commit comments