Skip to content

Commit 32ed917

Browse files
docs: update for Diagram.cascade() classmethod and prune() restrictions
- cascade() is now a classmethod: Diagram.cascade(table_expr) builds the full cascade diagram in one call, including all descendants across loaded schemas. - restrict() is an instance method on schema Diagrams. - prune() only works on restrict/unrestricted Diagrams, not cascade. - Remove dry_run references; document safemode workflow instead. - Update all code examples to new API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 287a6cc commit 32ed917

2 files changed

Lines changed: 19 additions & 25 deletions

File tree

src/about/whats-new-22.md

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -217,15 +217,11 @@ In 2.2, `Table.delete()` and `Table.drop()` use `dj.Diagram` internally to compu
217217

218218
### The Preview-Then-Execute Pattern
219219

220-
The key benefit of the diagram-level API is the ability to build a cascade explicitly, inspect it, and then execute via `Table.delete()`:
220+
`Diagram.cascade()` is a class method that builds a complete cascade diagram from a table expression — including all descendants across all loaded schemas — in a single call:
221221

222222
```python
223-
# Build the dependency graph and inspect the cascade
224-
diag = dj.Diagram(schema)
225-
restricted = diag.cascade(Session & {'subject_id': 'M001'})
226-
227-
# Inspect: what tables and how many rows would be affected?
228-
counts = restricted.counts()
223+
# Preview: what tables and how many rows would be affected?
224+
dj.Diagram.cascade(Session & {'subject_id': 'M001'}).counts()
229225
# {'`lab`.`session`': 3, '`lab`.`trial`': 45, '`lab`.`processed_data`': 45}
230226

231227
# Execute via Table.delete() after reviewing the blast radius
@@ -238,17 +234,17 @@ This is valuable when working with unfamiliar pipelines, large datasets, or mult
238234

239235
The diagram supports two restriction propagation modes designed for fundamentally different tasks.
240236

241-
**`cascade()` prepares a delete.** It takes a single restricted table expression, propagates the restriction downstream through all descendants, and **trims the diagram** to the resulting subgraph — ancestors and unrelated tables are removed entirely. Convergence uses OR: a descendant row is marked for deletion if *any* ancestor path reaches it, because if any reason exists to remove a row, it should be removed. `cascade()` is one-shot and is always followed by `counts()` or `delete()`.
237+
**`Diagram.cascade(table_expr)`** is a class method that creates a cascade diagram for delete. It takes a (possibly restricted) table expression, includes all descendants across loaded schemas, propagates the restriction downstream, and **trims the diagram** to the resulting subgraph — ancestors and unrelated tables are removed entirely. Convergence uses OR: a descendant row is marked for deletion if *any* ancestor path reaches it, because if any reason exists to remove a row, it should be removed.
242238

243239
When the cascade encounters a part table whose master is not yet included in the cascade, the behavior depends on the `part_integrity` setting. With `"enforce"` (the default), `delete()` raises an error if part rows would be deleted without their master — preventing orphaned master rows. With `"cascade"`, the restriction propagates *upward* from the part to its master: the restricted part rows identify which master rows are affected, those masters receive a restriction, and that restriction then propagates back downstream to all sibling parts — deleting the entire compositional unit, not just the originally matched part rows.
244240

245-
**`restrict()` selects a data subset.** It propagates a restriction downstream but **preserves the full diagram**, allowing `restrict()` to be called again from a different seed table. This makes it possible to build up multi-condition subsets incrementally — for example, restricting by species from one table and by date from another. Convergence uses AND: a descendant row is included only if *all* restricted ancestors match, because an export should contain only rows satisfying every condition. After chaining restrictions, use `prune()` to remove empty tables and `counts()` to inspect the result.
241+
**`diagram.restrict(table_expr)`** is an instance method that selects a data subset. It propagates a restriction downstream but **preserves the full diagram**, allowing `restrict()` to be called again from a different seed table. This makes it possible to build up multi-condition subsets incrementally — for example, restricting by species from one table and by date from another. Convergence uses AND: a descendant row is included only if *all* restricted ancestors match, because an export should contain only rows satisfying every condition. After chaining restrictions, use `prune()` to remove empty tables and `counts()` to inspect the result.
246242

247-
The two modes are mutually exclusive on the same diagram — DataJoint raises an error if you attempt to mix `cascade()` and `restrict()`, or if you call `cascade()` more than once. This prevents accidental mixing of incompatible semantics: a delete diagram should never be reused for subsetting, and vice versa.
243+
The two modes are mutually exclusive `restrict()` raises an error if called on a Diagram produced by `cascade()`. This prevents accidental mixing of incompatible semantics: a delete diagram should never be reused for subsetting.
248244

249245
### Pruning Empty Tables
250246

251-
After applying restrictions, some tables in the diagram may have zero matching rows. The `prune()` method removes these tables from the diagram, leaving only the subgraph with actual data:
247+
After applying restrictions with `restrict()`, some tables in the diagram may have zero matching rows. The `prune()` method removes these tables from the diagram, leaving only the subgraph with actual data:
252248

253249
```python
254250
export = (dj.Diagram(schema)
@@ -262,6 +258,8 @@ export # visualize the export subgraph
262258

263259
Without prior restrictions, `prune()` removes physically empty tables. This is useful for understanding which parts of a pipeline are populated.
264260

261+
`prune()` cannot be used on cascade Diagrams — cascade retains all descendant tables to handle concurrent inserts safely (a table empty at cascade time could have rows by the time `delete()` executes).
262+
265263
### Restriction Propagation Rules
266264

267265
When `cascade()` or `restrict()` propagates a restriction from a parent to a child, one of three rules applies depending on the foreign key relationship:
@@ -287,11 +285,10 @@ With `safemode=True` (the default), `delete()` provides a built-in preview-and-c
287285

288286
This is safer than a pre-transaction preview because it reflects the actual database state at delete time, including triggers and concurrent changes.
289287

290-
For programmatic preview without executing, use `Diagram` directly:
288+
For programmatic preview without executing, use `Diagram.cascade()`:
291289

292290
```python
293-
diag = dj.Diagram(schema)
294-
counts = diag.cascade(Session & {'subject_id': 'M001'}).counts()
291+
dj.Diagram.cascade(Session & {'subject_id': 'M001'}).counts()
295292
# {'`lab`.`session`': 3, '`lab`.`trial`': 45, '`lab`.`processed_data`': 45}
296293
```
297294

@@ -319,7 +316,7 @@ Each yielded `FreeTable` carries any cascade or restrict conditions that have be
319316

320317
### Architecture
321318

322-
`Table.delete()` constructs a `Diagram` internally, calls `cascade()` to compute the affected subgraph, then iterates `reversed(diagram)` to delete leaves first. The Diagram is purely a graph computation and inspection tool — it computes the cascade and provides `counts()` and iteration, but all mutation logic (transactions, SQL execution, prompts) lives in `Table.delete()` and `Table.drop()`.
319+
`Table.delete()` uses `Diagram.cascade(self)` internally to compute the affected subgraph, then iterates `reversed(diagram)` to delete leaves first. `Table.drop()` builds a Diagram with all descendants and drops in the same order. The Diagram is purely a graph computation and inspection tool — it computes the cascade and provides `counts()` and iteration, but all mutation logic (transactions, SQL execution, prompts) lives in `Table.delete()` and `Table.drop()`.
323320

324321
### Advantages over Error-Driven Cascade
325322

src/how-to/delete-data.md

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,23 +196,20 @@ print(f"Deleted {count} subjects")
196196

197197
With `safemode=True` (the default), `delete()` provides a built-in safety workflow: it executes the cascade inside a transaction, shows all affected tables and row counts, and asks **"Commit deletes?"** before committing. Declining rolls back all changes.
198198

199-
For programmatic preview without executing, or for complex scenarios — working across schemas, chaining multiple restrictions, or visualizing the dependency graph — use `dj.Diagram` to build and inspect the cascade explicitly:
199+
For programmatic preview without executing, or for complex scenarios — working across schemas or visualizing the dependency graph — use `Diagram.cascade()` to inspect the cascade:
200200

201201
```python
202202
import datajoint as dj
203203

204-
# 1. Build the dependency graph and apply cascade restriction
205-
diag = dj.Diagram(schema)
206-
restricted = diag.cascade(Session & {'subject_id': 'M001'})
207-
208-
# 2. Preview: see affected tables and row counts
209-
counts = restricted.counts()
204+
# 1. Preview: see affected tables and row counts (across all loaded schemas)
205+
cascade = dj.Diagram.cascade(Session & {'subject_id': 'M001'})
206+
counts = cascade.counts()
210207
# {'`lab`.`session`': 3, '`lab`.`trial`': 45, '`lab`.`processed_data`': 45}
211208

212-
# 3. Visualize the cascade subgraph (in Jupyter)
213-
restricted
209+
# 2. Visualize the cascade subgraph (in Jupyter)
210+
cascade
214211

215-
# 4. Execute via Table.delete() after reviewing
212+
# 3. Execute via Table.delete() after reviewing
216213
(Session & {'subject_id': 'M001'}).delete(prompt=False)
217214
```
218215

0 commit comments

Comments
 (0)