Repository navigation
Build datasets in notebooks without ipywidgets; fix datasets overview example - #1260
Conversation
… example In a Jupyter kernel, dask's progress() draws an ipywidgets bar. ipywidgets is not a PyHealth dependency, so building any dataset's event cache in a notebook without it (e.g. a plain venv kernel in VS Code or Cursor) failed with ModuleNotFoundError and deleted the half-built cache. - base_dataset: pass notebook=False to dask's progress() when ipywidgets is not importable, so dask falls back to its text bar. - In notebooks (threaded workers), wait 0.3 s after the compute so closing the cluster does not race dask's 100 ms progress feed and log a spurious CancelledError traceback. - tests: simulate a kernel without ipywidgets; the build succeeds and the scheduler logs no errors (fails on master with ModuleNotFoundError). - examples/datasets_overview.ipynb: the synthetic-samples cell called SampleDataset(samples=...), which no longer exists; use create_sample_dataset. Its stale error output is cleared. - docs/install.rst: note on notebooks and the optional ipywidgets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
/review |
There was a problem hiding this comment.
Previous review
AI review: findings
The PR fixes dataset builds in notebook kernels that lack ipywidgets: base_dataset.py passes notebook=False to dask's progress() when ipywidgets is not importable, and in notebooks adds a 0.3 s sleep before cluster shutdown to avoid a spurious CancelledError log. It also replaces a stale SampleDataset(...) call in examples/datasets_overview.ipynb with create_sample_dataset(...), which exists with matching arguments, and adds a docs note in docs/install.rst. No existing helper in the repo duplicates the dask progress selection, so the change is not redundant. Tests: a new notebook-path test in tests/core/test_base_dataset.py simulates a kernel without ipywidgets and checks the build succeeds with no scheduler ERROR logs; the PR says it fails on master and passes here. No blocking problems found. Non-blocking: the 0.3 s sleep is a timing workaround that no test isolates, so nothing verifies it is what prevents the logged traceback. Also non-blocking: the docs claim about "2.0.2 and earlier" is broader than the PR's evidence, which only covers 2.0.2. Overall risk is low: behavior changes only in notebooks, and with ipywidgets installed nothing changes.
Reviewed commit ce54889
There was a problem hiding this comment.
Previous review
AI Review
suggestion: human review
decision: not available
Haiku found that this change needs a human review: The change edits BaseDataset._event_transform directly, the dataset-build path every BaseDataset subclass runs through. It alters behavior for notebook users (progress-bar selection and a post-compute sleep), so a human should confirm the change is safe for the shared build path.
Reviewed commit ce54889
Test CoverageTotal: 69.06% (17732 / 25678 lines)
|
…rsions - The wait after a notebook build is now a multiple of the progress interval, which progress() is given explicitly, and the comment names the cause: in distributed 2026.8, Scheduler.feed sleeps one interval between updates and stops only when its next write finds the bar's connection closed, so closing the threaded cluster during that sleep logs a CancelledError. distributed has no public way to wait for the feed. - The test's comment says why it watches distributed.scheduler: that is where the CancelledError is logged, so the test fails without the wait. - docs/install.rst names the releases with the unguarded progress() call, 2.0.0 to 2.1.0 (1.x does not use dask). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ntMLEmFsQEQJKTmPGGw4T
|
Thanks for the review. Both points are addressed in 5e2f75e. The 0.3 s sleep after a notebook build
Docs version claim The unguarded |
|
/review |
There was a problem hiding this comment.
Previous review
AI review: findings
The PR makes BaseDataset._event_transform (pyhealth/datasets/base_dataset.py), the event-cache build every dataset uses, work in notebooks without ipywidgets. When ipywidgets is not importable it passes notebook=False to dask's progress(), and in notebooks it sleeps 0.3 s after compute to avoid a distributed CancelledError at cluster close. It also replaces the stale SampleDataset(samples=...) call in examples/datasets_overview.ipynb with create_sample_dataset, and adds an install note. The new regression test simulates a kernel without ipywidgets and asserts the build succeeds with no distributed.scheduler ERROR logs; the PR reports it fails on master and passes with the change, though I could not run it. Nothing in the repo already does this fallback, so it is not redundant. Blocking problems: none. Non-blocking: the fixed time.sleep(0.3) in base_dataset.py is a timing workaround with no deterministic wait, and the regression test's no-error assertion depends on it. Also non-blocking: the notebook comment says SampleDataset is on disk while create_sample_dataset defaults to in_memory=True, and the install note's "2.0.0 to 2.1.0" range is not backed by anything in the PR. Overall risk is low; the change is small and confined to the notebook build path.
Reviewed commit 5e2f75e
There was a problem hiding this comment.
Previous review
AI Review
suggestion: human review
decision: not available
Haiku found that this change needs a human review: The change edits BaseDataset._event_transform, the shared event-cache build that every dataset subclass relies on. It changes the dask progress call and adds a sleep on the notebook path, so it changes shared behavior rather than a self-contained component.
Reviewed commit 5e2f75e
…aset create_sample_dataset defaults to in_memory=True and returns an InMemorySampleDataset; the comment said the dataset was stored on disk. It now names in_memory=False for the disk-backed SampleDataset. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ntMLEmFsQEQJKTmPGGw4T
|
Thanks for the second review. All three points are covered in 3ed7c98 and the updated PR description. Notebook comment ( Fixed. The comment now says that Affected versions ( The range comes from the release tags. The PR description now gives the evidence:
PyHealth 1.x has no I kept the note in the install guide rather than moving it to The 0.3 s wait It stays for now. I looked for a real synchronization point in distributed 2026.8.0 and didn't find one:
So the wait is three progress intervals. It is passed to One alternative is a logging filter on What would let the wait go: a distributed release in which the feed ends cleanly when the cluster closes. For example, |
|
/review |
There was a problem hiding this comment.
AI review: findings
The PR makes BaseDataset's event-cache build work in notebooks without ipywidgets. In pyhealth/datasets/base_dataset.py, _event_transform now passes notebook=False to dask's progress() when ipywidgets is not importable, and sleeps 0.3 s after the compute in notebooks so distributed's progress feed does not log a CancelledError when the threaded cluster closes. It also replaces the stale SampleDataset(samples=...) cell in examples/datasets_overview.ipynb with create_sample_dataset (its signature accepts dataset_name and in_memory), and adds a note to docs/install.rst. No public API changes; the change reuses litdata's in_notebook and there is no existing dask-progress helper, so nothing is redundant. Blocking problems: none found. Non-blocking (low): the 0.3 s sleep is a timing heuristic tied to distributed 2026.8 internals; it reduces the spurious log but does not guarantee its absence under load (base_dataset.py:750). Non-blocking (low): the new test's patch.dict(sys.modules, ...) restores sys.modules on exit and can drop modules first imported inside the block (tests/core/test_base_dataset.py:178). The new test mirrors the reported failure and follows the file's MockDataset pattern, but its no-ERROR-log assertion is timing-dependent, so it guards the race only probabilistically. I could not run the test suite (no execution tool), so the author's reported results are unverified. The repo is at version 2.1.0, consistent with the docs' affected-range note; I could not check the release tags. Overall risk is low.
Reviewed commit 3ed7c98
| # that sleep cancels it, and distributed logs the | ||
| # CancelledError as an error. distributed has no public | ||
| # way to wait for the feed, so wait a few intervals. | ||
| time.sleep(3 * _DASK_PROGRESS_INTERVAL) |
There was a problem hiding this comment.
[low] Fixed 0.3 s sleep is a timing heuristic, not a guarantee
The wait time.sleep(3 * _DASK_PROGRESS_INTERVAL) only reduces the chance that distributed's progress feed is still sleeping when the threaded LocalCluster closes. If the scheduler is slow under load, the spurious CancelledError log can still appear, though the build still succeeds. The comment is accurate about the mechanism, and the author says distributed has no public way to wait, so this is an acceptable workaround. Consider re-checking it when distributed is upgraded, and keep the comment tied to the distributed version.
| "pyhealth.datasets.base_dataset.in_notebook", return_value=True | ||
| ), patch( | ||
| "distributed.diagnostics.progressbar.is_kernel", return_value=True | ||
| ), patch.dict(sys.modules, {"ipywidgets": None}), self.assertNoLogs( |
There was a problem hiding this comment.
[low] patch.dict(sys.modules) can drop modules imported inside the block
On exit, patch.dict(sys.modules, {...}) restores the whole dict, which removes any module first imported while the block runs (for example, lazily imported dask or distributed submodules during the LocalCluster build). Later tests may then re-import those modules as new objects, which can cause order-dependent failures. Fix by blocking only the ipywidgets entry, for example by setting sys.modules['ipywidgets'] = None in a try/finally that restores the previous value, or by patching builtins.import for that name.
There was a problem hiding this comment.
AI Review
suggestion: human review
decision: not available
Haiku found that this change needs a human review: The change modifies BaseDataset._event_transform in pyhealth/datasets/base_dataset.py, the build path every dataset uses. It changes how dask progress is drawn and adds a notebook-only sleep to the cache build, so the shared base-class behaviour changes for all datasets built in notebooks.
Reviewed commit 3ed7c98
Problem
When a dataset builds its event cache in a Jupyter kernel,
BaseDatasetcalls dask'sprogress(). In a kernel, dask draws an ipywidgets progress bar, butipywidgetsis not a PyHealth dependency. In a notebook without it, such as a plain venv kernel in VS Code or Cursor, building any dataset fails:The same unguarded call is in every 2.x release, so 2.0.0 to 2.1.0 are affected. I checked the release tags:
git show <tag>:pyhealth/datasets/base_dataset.pyhas a baredask_progress(handle)at line 500 in v2.0.0, 524 in v2.0.1, 595 in v2.0.2 and 712 in v2.1.0. PyHealth 1.x has nobase_dataset.pyand doesn't use dask.Changes
base_dataset.py:ipywidgetsisn't importable, passnotebook=Falseto dask'sprogress(), so dask falls back to its text bar. Withipywidgetsinstalled, behaviour is unchanged.Scheduler.feed) sleeps one interval between updates and stops only when its next write finds the bar's connection closed. Closing the threaded cluster during that sleep cancels the feed, and distributed logs a spuriousCancelledErrortraceback, even though the data was written correctly. distributed has no public way to wait for the feed, so this is a timing workaround. It only runs in notebooks: console builds, with worker processes, didn't hit the race (0 of 6 runs).tests/core/test_base_dataset.py: simulates a kernel withoutipywidgets, as in Jupyter (threaded workers, kernel detection on). It asserts that the build succeeds and that the dask scheduler logs no errors. The test fails on master with theModuleNotFoundErrorabove and passes with this change.examples/datasets_overview.ipynb: the synthetic-samples cell calledSampleDataset(samples=…). That constructor no longer exists; the committed output already showed an error, and running it today raisesTypeError: … missing … 'path'. It now usescreate_sample_dataset(...), and its stale error output is cleared. The new cell runs and printsLoaded 2 synthetic samples.No other cell changed.docs/install.rst: a note on notebooks and the optionalipywidgets, under Platform-Specific Notes, naming the affected versions (2.0.0 to 2.1.0, from the tags above).Verification
tools/check_pr_rules.py: all rules pass.CancelledErrorin 8 of 8 runs, and the new test failed 3 of 3. With the wait: 0 of 8, and the test passed 5 of 5.test_medcode_icd_mappings(medcode: ICD-9 <-> ICD-10 translation and grouper vocabularies via icd-mappings #1257), which fail locally only becauseicd-mappingswasn't installed in my older environment. With it installed, those tests andtest_base_datasetpass (47/47).🤖 Generated with Claude Code