@@ -99,13 +99,75 @@ def _rename_attributes(table, props):
99
99
100
100
def make (self , key ):
101
101
"""
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.
105
144
"""
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
109
171
110
172
@property
111
173
def target (self ):
@@ -372,6 +434,8 @@ def _populate1(
372
434
]
373
435
): # rollback due to referential integrity fail
374
436
self .connection .cancel_transaction ()
437
+ logger .warning (
438
+ f"Referential integrity failed for { key } -> { self .target .full_table_name } " )
375
439
return False
376
440
gen .send (computed_result ) # insert
377
441
0 commit comments