Honor the download-flag contract across all datasets and harden the NEMAR sourcedata path - #1146
Honor the download-flag contract across all datasets and harden the NEMAR sourcedata path#1146bruAristimunha wants to merge 9 commits into
Conversation
…urcedata path Flag contract: - CacheConfig.overwrite_* now erases the cache even when use=False (previously a silent no-op with the default config); docstring and make() examples corrected to match behavior. - CompoundDataset.data_path forwards path/force_update/update_path/verbose to the wrapped dataset instead of dropping all four. - data_path implementations that ignored path or force_update now honor them: Weibo2014, alphawaves, bbci_eeg_fnirs (Shin2017 helpers), beetl, chailloux2020, dreyer2023, epfl, erpcore2021, hinss2021, huebner_llp, kaneshiro2015, lee2024, liu2024, mainsah2025, phmd_ml, physionet_mi, sosulski2019, speier2017, ssvep_mamem, stieger2021, zhang2025, zheng2020. - Kojima2024a/b data_path regains the standard signature; its narrowed form made BaseDataset.download() raise TypeError. - force_update also re-extracts stale zip folders; download_if_missing gains a force_update parameter as the choke point for manifest-based datasets. - New AST regression test enforces that every dataset's data_path uses path and force_update; new test proves overwrite works with use=False. NEMAR sourcedata: - download() falls back to the upstream host per failing subject instead of discarding the whole NEMAR batch; the unused batch helper is removed. - sourcedata_path resolves subjects through nemar_subject_template, matching the sibling BIDS path. - Transport/verification failures are no longer misreported as 'deposit publishes no sourcedata'; SelectionError keeps that message. - Manifest filenames are glob-escaped before being used as include patterns and local verification globs. - Stale nemar_sourcedata_include changelog claim replaced with the real provenance mechanism; nemar_dl/nemar_sourcedata_dl added to api.rst; docstrings no longer promise a NemarDownloadError for a missing nemar-py (that raises ImportError at import time). - Direct round-trip tests for set/get_download_provider (validation, normalization, None reset, env-var precedence, unknown-value fallback) and a per-subject fallback test.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…anifest errors honestly Real provenance manifests (nm000266, nm000221) key subjects by the raw MOABB id, while nemar_subject_template documents how a deposit labels its subjects -- so sourcedata_path now offers both forms as aliases and _sourcedata_files_for_subject matches any of them, instead of betting on a single convention. Manifest entries without a usable 'file' key are skipped rather than raising KeyError, and a missing sourcedata_provenance.json (SelectionError) is now distinguished from a transport failure when fetching the manifest.
|
Follow-up push (bd7c95c) after verifying against live NEMAR deposits:
Still deliberately open (needs a design decision): wiring the NEMAR sourcedata cache into |
The reader was designed for the original distribution where each of the
seven components was a standalone BIDS dataset in its own
MNE-erpcore{task}2021-data folder with a per-task MNE config sign. The
dataset is one BIDS tree whose components are separated by the task-
entity (as the NEMAR deposit nm000132 also publishes it), so all seven
classes now share a single MNE-erpcore2021-data root under one
ERPCORE2021 sign; BIDSPath(task=...) already selects each component's
files, and the per-component filenames never collide because each
carries its task. A pre-existing download in the legacy separated layout
is detected per subject and read as-is instead of re-fetching. The load
path is unchanged apart from reading from the shared root.
|
Third commit (3627d8d): ERP CORE combined-layout fix. The reader was built for the original distribution where every component was its own standalone BIDS dataset (seven
A side benefit: the local layout now mirrors nm000132's combined BIDS tree one-to-one, which makes eventually wiring the NEMAR BIDS copy into this reader a root-swap rather than a restructure. |
…ranges
Same disease as ERP CORE's separated layout: the A/B/C classes and the
combined class all read the same globally numbered deposit (nm000250,
one OSF manifest keyed by sub-NN.zip), yet each stored into its own
MNE-Dreyer2023{X}-data folder -- up to four copies of the same files and
dataset-level assets. All four classes now share MNE-dreyer2023-data
under one Dreyer2023 config sign; a pre-existing legacy per-class
download is detected per subject and read as-is. get_subject_info moves
to the shared root too. A sweep of every multi-class dataset module
confirms no other family splits one source across per-code folders
(Shin2017, BEETL and Huebner already share a root; Lee2019, Kojima2024,
MAMEM and BrainInvaders classes fetch genuinely distinct deposits).
|
Fourth commit (833afc5): the layout audit found one more family with ERP CORE's disease — Dreyer2023. Systematic sweep: grouped every dataset class by NEMAR deposit and scanned every multi-class module for per-
Everything else checked clean: Shin2017, BEETL and Huebner already share a root; Lee2019, Kojima2024, MAMEM and BrainInvaders classes fetch genuinely distinct deposits, so their per-code folders are correct. Full suites: 1470 passed, pre-commit clean. |
|
Coverage verification, per class rather than per file, since most of MOABB's 167 registered dataset classes (161 regular + 6 compound) inherit a shared
The file-based AST regression test in On "why not all datasets" for the NEMAR side: 73 of 161 classes declare a |
|
Correction to my previous comment: the "73 of 161 classes declare a nemar_id" figure was wrong — that grep only matched Counted properly at class level (resolved This changes the weight of the open wiring question: NEMAR coverage is nearly universal, so |
data_path now forwards force_update to download_if_missing, so the monkeypatched stand-in must take keyword arguments. This was the single real CI failure; the other five matrix legs were fail-fast cancellations cascading from it.
The store fetched by download() landed in a layout no loader read, so get_data() re-contacted the upstream host and NEMAR mirroring protected nothing (gh-1147). Loading now resolves files from the governed store first, with the URL-derived trees demoted to lookups so existing installs never re-download. Mechanics, kept deliberately small after review: - BaseDataset._sourcedata_store() is the one place that resolves a dataset's store (nemar_id + provider policy + shared nemar_store() root used by both writer and readers). - The loader publishes that store via a contextvar around _get_single_subject_data; data_dl, the deprecated data_path, and download_if_missing (manifest-based datasets) each consult it with a one-line hook. - The store keeps the upstream relative layout, so files are found by probing the trailing URL-path segments -- no globbing, no ranking. - force_update refetches upstream; provider 'upstream' opts loading out. Verified live on the gh-1147 reproducer: Schirrmeister2017 download() fetched 490 MB from NEMAR (subject 1); get_data() then loaded both runs in 1s with all sockets blocked.
Serving the store path directly let datasets that move their downloaded files (Schirrmeister2017 does shutil.move on data_dl's return) gut the store on first use -- caught by re-running the live gh-1147 reproducer. data_dl and the deprecated data_path now hardlink (copy on cross-device) the store file into the normal URL-derived destination and return that, so the dataset owns its copy, the store stays pristine, and the return contract is byte-identical to a normal download. Also per review: drop the compound-dataset wraps and the download_if_missing hook to keep the wiring surgical, and dedupe the test doubles into two module-level helpers with a socket-level network guard. Verified live twice in a row on Schirrmeister2017 with all sockets blocked; the store survives the dataset's file moves.
- The ErpCore2021 and Dreyer2023 split-family pairs collapse into one spec table, one shared fake downloader, and two parametrized tests. - The three data_dl source-resolution scenarios (store hit, no active store, force_update) become one parametrized test; the fake retrieve now honors known_hash like pooch so a filled destination is kept. - The gh-1147 move-survival regression keeps its own test; the network guard and retrieve fake are two shared module-level helpers, imports hoisted to the top of the file. - The sourcedata subject-alias test parametrizes over the default and a custom nemar_subject_template.
What
Two related fix-sets, landed together because they share the same download layer (
datasets/base.py,datasets/download.py):1. The download-flag contract — enforced for every dataset
An AST audit of every
data_pathimplementation found flags accepted but silently ignored across the catalogue:CacheConfig.overwrite_*was a no-op with the defaultuse=False—get_data(cache_config=dict(overwrite_raw=True))erased nothing. The docstring also claimedsave_*was ignored in that case while the code honored it. Overwrite now always erases; docs match behavior.CompoundDataset.data_pathdropped all four flags when delegating to the wrapped dataset.data_pathimplementations ignoredpathand/orforce_update(full list in the commit message) — including every recent dataset that resolved its storage dir viaget_dataset_path(SIGN, None)with the user'spathhardcoded away.data_pathsignature to(subject, path), soBaseDataset.download()raisedTypeError: unexpected keyword argument 'force_update'.force_updatenow also re-extracts stale archives;download_if_missinggains aforce_updateparameter as the choke point for the manifest-based datasets (dreyer2023, erpcore2021, kojima2024a/b, mainsah2025).update_pathis left as documented-deprecated (download.py: "Unused, kept for compatibility purpose").Guard rails: a new AST regression test fails CI if any dataset's
data_pathever ignorespathorforce_updateagain (exempt: the abstract base andFakeDataset), and a new cache test provesoverwrite_raw=True, use=Falseactually erases.2. NEMAR sourcedata hardening (follow-ups to #1139)
data_path, keeping what NEMAR already served. The now-unused batch helper_download_nemar_sourcedatais removed.sourcedata_pathresolves subjects throughnemar_subject_template, matching the sibling BIDS path (_download_nemar). Datasets likeErpCore2021({subject:03d}) previously sent the raw int to the provenance matcher.SelectionErrormaps to "deposit publishes no sourcedata"; transport/verification/S3 failures now report as download failures with the original error text.glob.escaped before being used as nemarincludepatterns and local verification globs, so upstream names containing[ ] * ?still match.nemar_sourcedata_includeattribute (never implemented) is replaced with the real provenance mechanism;nemar_dl/nemar_sourcedata_dlare registered inapi.rst; docstrings no longer promise aNemarDownloadErrorfor a missing nemar-py (that raisesImportErrorat import time).set_download_provider/get_download_provider(previously only exercised through monkeypatched seams): validation, case normalization,Nonereset, env-var precedence, unknown-value fallback — using the isolated MNE config fixture so nothing touches the real~/.mneconfig.Not in scope (deliberate, candidates for follow-ups)
provider=override / context manager instead of the persisted global (set_download_providerstill writes the MNE config).import nemar(todayimport moabbrequires nemar-py even withprovider="upstream").data_path/get_dataso a prefetch warms the evaluation path — needs a design decision on cache-location mapping.verbosein the pooch-based downloaders (progress bars are hardcoded; no slot to honor it).Test plan
pytest moabb/tests/test_download.py moabb/tests/test_datasets.py -k 'not doi'→ 1464 passedpytest moabb/tests/test_paradigms.py→ 102 passed;pytest moabb/tests/test_evaluations.py→ 93 passedpath+force_updatepre-commit run --all-files→ clean