Skip to content

Commit 5f37f83

Browse files
authored
Merge pull request #1241 from dimitri-yatsenko/master
document autopopulate.make logic
2 parents 7bc725d + e71418d commit 5f37f83

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
@@ -95,13 +95,75 @@ def _rename_attributes(table, props):
9595

9696
def make(self, key):
9797
"""
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.
101140
"""
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
105167

106168
@property
107169
def target(self):
@@ -352,6 +414,8 @@ def _populate1(
352414
]
353415
): # rollback due to referential integrity fail
354416
self.connection.cancel_transaction()
417+
logger.warning(
418+
f"Referential integrity failed for {key} -> {self.target.full_table_name}")
355419
return False
356420
gen.send(computed_result) # insert
357421

0 commit comments

Comments
 (0)