Skip to content

Commit a47856f

Browse files
authored
Merge branch 'main' into fix/tsv-encoding-detection
2 parents fe54566 + 515db44 commit a47856f

4 files changed

Lines changed: 58 additions & 30 deletions

File tree

doc/whats_new.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Detailed list of changes
4848
^^^^^^^^^^^^^^^^^^^^^^^^^^^
4949

5050
- Expected format conversions notices are now logged at ``info`` instead of ``warn`` level, by `Bruno Aristimunha`_ (:gh:`1589`)
51+
- Add ``readme`` parameter to :func:`mne_bids.write_raw_bids`; pass ``readme=False`` to leave any existing ``README`` untouched and skip creating one, by `Bruno Aristimunha`_ (:gh:`1550`)
52+
- :func:`mne_bids.make_dataset_description` preserves BIDS-spec keys it does not model when merging with an existing ``dataset_description.json``, by `Bruno Aristimunha`_ (:gh:`1548`)
5153

5254
🛠 Requirements
5355
^^^^^^^^^^^^^^^

mne_bids/tests/test_write.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,19 @@ def test_make_dataset_description(tmp_path, monkeypatch):
484484
make_dataset_description(path=tmp_path, name="tst")
485485

486486

487+
def test_make_dataset_description_preserves_unknown_keys(tmp_path):
488+
"""Unmodeled keys in dataset_description.json survive merges (gh:1548)."""
489+
fname = tmp_path / "dataset_description.json"
490+
make_dataset_description(path=tmp_path, name="enriched", overwrite=True)
491+
desc = json.loads(fname.read_text())
492+
desc["Description"] = "Free-form custom field"
493+
fname.write_text(json.dumps(desc))
494+
make_dataset_description(path=tmp_path, name="[Unspecified]", overwrite=False)
495+
final = json.loads(fname.read_text())
496+
assert final["Description"] == "Free-form custom field"
497+
assert final["Name"] == "enriched"
498+
499+
487500
def test_stamp_to_dt():
488501
"""Test conversions of meas_date to datetime objects."""
489502
meas_date = (1346981585, 835782)
@@ -802,6 +815,12 @@ def test_fif(_bids_validate, tmp_path):
802815
assert REFERENCES["mne-bids"] in text
803816
assert REFERENCES["meg"] in text
804817

818+
snapshot = Path(readme).read_bytes()
819+
write_raw_bids(
820+
raw, bids_path2, events=events, event_id=event_id, overwrite=True, readme=False
821+
)
822+
assert Path(readme).read_bytes() == snapshot
823+
805824
with pytest.raises(ValueError, match="raw_file must be"):
806825
write_raw_bids("blah", bids_path)
807826

mne_bids/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,13 @@ def _write_tsv(fname, dictionary, *, overwrite=False, lock=True, verbose=None):
257257
logger.info(f"Writing '{fname}'...")
258258

259259

260-
def _write_text(fname, text, overwrite=False):
260+
def _write_text(fname, text, overwrite=False, lock=True):
261261
"""Write text to a file."""
262262
if fname.exists() and not overwrite:
263263
raise FileExistsError(
264264
f'"{fname}" already exists. Please set overwrite to True.'
265265
)
266-
with _open_lock(fname, "w", encoding="utf-8-sig") as fid:
266+
with _open_lock(fname, "w", encoding="utf-8-sig", lock=lock) as fid:
267267
fid.write(text)
268268
fid.write("\n")
269269

mne_bids/write.py

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -590,44 +590,38 @@ def _events_json(
590590
_write_json(fname, new_data, overwrite=overwrite)
591591

592592

593-
def _readme(datatype, fname, overwrite=False):
593+
def _readme(datatype, fname):
594594
"""Create a README file and save it.
595595
596-
This will write a README file containing an MNE-BIDS citation.
597-
If a README already exists, the behavior depends on the
598-
``overwrite`` parameter, as described below.
596+
If a README already exists, append an MNE-BIDS citation to it
597+
unless one is already present. Otherwise, create a new README
598+
containing an MNE-BIDS citation.
599599
600600
Parameters
601601
----------
602602
datatype : string
603603
The type of data contained in the raw file ('meg', 'eeg', 'ieeg')
604604
fname : str | mne_bids.BIDSPath
605605
Filename to save the README to.
606-
overwrite : bool
607-
Whether to overwrite the existing file (defaults to False).
608-
If overwrite is True, create a new README containing an
609-
MNE-BIDS citation. If overwrite is False, append an
610-
MNE-BIDS citation to the existing README, unless it
611-
already contains that citation.
612606
"""
613-
if fname.is_file() and not overwrite:
614-
with _open_lock(fname, encoding="utf-8-sig") as fid:
615-
orig_data = fid.read()
616-
mne_bids_ref = REFERENCES["mne-bids"] in orig_data
617-
datatype_ref = REFERENCES[datatype] in orig_data
607+
# Hold the lock across read and write so concurrent writers cannot
608+
# observe a partially written file.
609+
with _open_lock(fname):
610+
if fname.is_file():
611+
text = fname.read_text("utf-8-sig")
612+
else:
613+
text = ""
614+
mne_bids_ref = REFERENCES["mne-bids"] in text
615+
datatype_ref = REFERENCES[datatype] in text
618616
if mne_bids_ref and datatype_ref:
619617
return
620-
text = "{}References\n----------\n{}{}".format(
621-
orig_data + "\n\n",
622-
"" if mne_bids_ref else REFERENCES["mne-bids"] + "\n\n",
623-
"" if datatype_ref else REFERENCES[datatype] + "\n",
624-
)
625-
else:
626-
text = "References\n----------\n{}{}".format(
627-
REFERENCES["mne-bids"] + "\n\n", REFERENCES[datatype] + "\n"
628-
)
629-
630-
_write_text(fname, text, overwrite=True)
618+
text += "\n\n" if text else ""
619+
text += "References\n----------\n"
620+
if not mne_bids_ref:
621+
text += REFERENCES["mne-bids"] + "\n\n"
622+
if not datatype_ref:
623+
text += REFERENCES[datatype] + "\n"
624+
_write_text(fname, text, overwrite=True, lock=False)
631625

632626

633627
def _participants_tsv(raw, subject_id, fname, overwrite=False):
@@ -1707,13 +1701,14 @@ def make_dataset_description(
17071701

17081702
# Handle potentially existing file contents
17091703
with _open_lock(fname):
1704+
orig_cols = {}
17101705
if op.isfile(fname):
17111706
try:
17121707
with open(fname, encoding="utf-8-sig") as fin:
17131708
orig_cols = json.load(fin)
17141709
except (json.JSONDecodeError, OSError):
17151710
# File is empty, corrupted, or being written to by another process
1716-
orig_cols = {}
1711+
pass
17171712
if "BIDSVersion" in orig_cols and orig_cols["BIDSVersion"] != BIDS_VERSION:
17181713
warnings.warn(
17191714
"Conflicting BIDSVersion found in dataset_description.json! "
@@ -1738,6 +1733,11 @@ def make_dataset_description(
17381733
pop_keys = [key for key, val in description.items() if val is None]
17391734
for key in pop_keys:
17401735
description.pop(key)
1736+
1737+
# Preserve BIDS-spec keys we do not model (e.g. Description, DatasetLinks).
1738+
for key, val in orig_cols.items():
1739+
description.setdefault(key, val)
1740+
17411741
_write_json(fname, description, overwrite=True, lock=False)
17421742

17431743

@@ -1761,6 +1761,7 @@ def write_raw_bids(
17611761
electrodes_tsv_task=False,
17621762
emg_placement=None,
17631763
overwrite=False,
1764+
readme=True,
17641765
verbose=None,
17651766
):
17661767
"""Save raw data to a BIDS-compliant folder structure.
@@ -1961,6 +1962,11 @@ def write_raw_bids(
19611962
and ``participants.tsv`` by a user will be retained.
19621963
If ``False``, no existing data will be overwritten or
19631964
replaced.
1965+
readme : bool
1966+
If ``False``, leave any existing ``README`` untouched and do
1967+
not create one. Defaults to ``True``.
1968+
1969+
.. versionadded:: 0.19
19641970
%(verbose)s
19651971
19661972
Returns
@@ -2345,7 +2351,8 @@ def write_raw_bids(
23452351
# save readme file unless it already exists
23462352
# XXX: can include README overwrite in future if using a template API
23472353
# XXX: see https://github.com/mne-tools/mne-bids/issues/551
2348-
_readme(bids_path.datatype, readme_fname, False)
2354+
if readme:
2355+
_readme(bids_path.datatype, readme_fname)
23492356

23502357
# save all participants meta data
23512358
_participants_tsv(

0 commit comments

Comments
 (0)