Skip to content

Commit eb90d3d

Browse files
authored
Merge branch 'datajoint:master' into autopopulate-2.0
2 parents e9f5377 + 5f37f83 commit eb90d3d

File tree

1 file changed

+70
-6
lines changed

1 file changed

+70
-6
lines changed

datajoint/autopopulate.py

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)